From 54d56627a395cdf9dfa451c3c315f12d1c0626a8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 22 Aug 2025 20:25:02 +0000 Subject: [PATCH 01/10] Initial plan From 736ae89d52f37dfb5539a74e639fcc43906ae837 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 22 Aug 2025 20:34:07 +0000 Subject: [PATCH 02/10] Initial repository exploration and validation completed Co-authored-by: KSemenenko <4385716+KSemenenko@users.noreply.github.com> --- .../ManagedCode.Communication.Tests.trx | 4263 +++++++++++++++++ dotnet-install.sh | 1888 ++++++++ 2 files changed, 6151 insertions(+) create mode 100644 ManagedCode.Communication.Tests/ManagedCode.Communication.Tests.trx create mode 100755 dotnet-install.sh diff --git a/ManagedCode.Communication.Tests/ManagedCode.Communication.Tests.trx b/ManagedCode.Communication.Tests/ManagedCode.Communication.Tests.trx new file mode 100644 index 0000000..31caa58 --- /dev/null +++ b/ManagedCode.Communication.Tests/ManagedCode.Communication.Tests.trx @@ -0,0 +1,4263 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + [xUnit.net 00:00:00.00] xUnit.net VSTest Adapter v3.1.4+50e68bbb8b (64-bit .NET 9.0.0) +[xUnit.net 00:00:00.10] Discovering: ManagedCode.Communication.Tests +[xUnit.net 00:00:00.21] Discovered: ManagedCode.Communication.Tests +[xUnit.net 00:00:00.28] Starting: ManagedCode.Communication.Tests +Value: 20 +info: Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager[62] + User profile is available. Using '/home/runner/.aspnet/DataProtection-Keys' as key repository; keys will not be encrypted at rest. +info: Microsoft.AspNetCore.Hosting.Diagnostics[1] + Request starting HTTP/1.1 GET http://localhost/test/collection-success - - - +info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0] + Executing endpoint 'ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.GetCollectionSuccess (ManagedCode.Communication.Tests)' +info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[102] + Route matched with {action = "GetCollectionSuccess", controller = "Test"}. Executing controller action with signature ManagedCode.Communication.CollectionResultT.CollectionResult`1[ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestModel] GetCollectionSuccess() on controller ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController (ManagedCode.Communication.Tests). +info: Microsoft.AspNetCore.Mvc.Infrastructure.ObjectResultExecutor[1] + Executing ObjectResult, writing value of type 'ManagedCode.Communication.CollectionResultT.CollectionResult`1[[ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestModel, ManagedCode.Communication.Tests, Version=9.6.2.0, Culture=neutral, PublicKeyToken=null]]'. +info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[105] + Executed action ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.GetCollectionSuccess (ManagedCode.Communication.Tests) in 110.497ms +info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1] + Executed endpoint 'ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.GetCollectionSuccess (ManagedCode.Communication.Tests)' +info: Microsoft.AspNetCore.Hosting.Diagnostics[2] + Request finished HTTP/1.1 GET http://localhost/test/collection-success - 200 - application/json;+charset=utf-8 206.6610ms +info: Microsoft.AspNetCore.Hosting.Diagnostics[1] + Request starting HTTP/1.1 POST http://localhost/test/validate - application/json;+charset=utf-8 38 +info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0] + Executing endpoint 'ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.Validate (ManagedCode.Communication.Tests)' +info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[102] + Route matched with {action = "Validate", controller = "Test"}. Executing controller action with signature Microsoft.AspNetCore.Mvc.ActionResult`1[System.String] Validate(ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestValidationModel) on controller ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController (ManagedCode.Communication.Tests). +warn: ManagedCode.Communication.AspNetCore.Filters.CommunicationModelValidationFilter[0] + Model validation failed for ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.Validate (ManagedCode.Communication.Tests) +info: Microsoft.AspNetCore.Mvc.Infrastructure.ObjectResultExecutor[1] + Executing BadRequestObjectResult, writing value of type 'ManagedCode.Communication.Result'. +info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[105] + Executed action ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.Validate (ManagedCode.Communication.Tests) in 38.1819ms +info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1] + Executed endpoint 'ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.Validate (ManagedCode.Communication.Tests)' +info: Microsoft.AspNetCore.Hosting.Diagnostics[2] + Request finished HTTP/1.1 POST http://localhost/test/validate - 400 - application/json;+charset=utf-8 51.8960ms +info: Microsoft.AspNetCore.Hosting.Diagnostics[1] + Request starting HTTP/1.1 GET http://localhost/test/custom-problem - - - +info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0] + Executing endpoint 'ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.CustomProblem (ManagedCode.Communication.Tests)' +info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[102] + Route matched with {action = "CustomProblem", controller = "Test"}. Executing controller action with signature Microsoft.AspNetCore.Mvc.ActionResult CustomProblem() on controller ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController (ManagedCode.Communication.Tests). +info: Microsoft.AspNetCore.Mvc.Infrastructure.ObjectResultExecutor[1] + Executing ObjectResult, writing value of type 'ManagedCode.Communication.Problem'. +info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[105] + Executed action ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.CustomProblem (ManagedCode.Communication.Tests) in 9.9844ms +info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1] + Executed endpoint 'ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.CustomProblem (ManagedCode.Communication.Tests)' +info: Microsoft.AspNetCore.Hosting.Diagnostics[2] + Request finished HTTP/1.1 GET http://localhost/test/custom-problem - 409 - application/json;+charset=utf-8 16.0905ms +info: Microsoft.AspNetCore.Hosting.Diagnostics[1] + Request starting HTTP/1.1 GET http://localhost/test/throw-exception - - - +info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0] + Executing endpoint 'ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.ThrowException (ManagedCode.Communication.Tests)' +info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[102] + Route matched with {action = "ThrowException", controller = "Test"}. Executing controller action with signature System.String ThrowException() on controller ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController (ManagedCode.Communication.Tests). +fail: ManagedCode.Communication.AspNetCore.Filters.CommunicationExceptionFilter[0] + Unhandled exception in Test.ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.ThrowException (ManagedCode.Communication.Tests) + System.InvalidOperationException: This is a test exception for integration testing + at ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.ThrowException() in /_/ManagedCode.Communication.Tests/Common/TestApp/Controllers/TestController.cs:line 130 + at lambda_method104(Closure, Object, Object[]) + at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.SyncObjectResultExecutor.Execute(ActionContext actionContext, IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments) + at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeActionMethodAsync() + at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted) + at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeNextActionFilterAsync() + --- End of stack trace from previous location --- + at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context) + at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted) + at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeInnerFilterAsync() + --- End of stack trace from previous location --- + at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextExceptionFilterAsync>g__Awaited|26_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted) +info: ManagedCode.Communication.AspNetCore.Filters.CommunicationExceptionFilter[0] + Exception handled by CommunicationExceptionFilter for Test.ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.ThrowException (ManagedCode.Communication.Tests) +info: Microsoft.AspNetCore.Mvc.Infrastructure.ObjectResultExecutor[1] + Executing ObjectResult, writing value of type 'ManagedCode.Communication.Result'. +info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[105] + Executed action ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.ThrowException (ManagedCode.Communication.Tests) in 17.1595ms +info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1] + Executed endpoint 'ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.ThrowException (ManagedCode.Communication.Tests)' +info: Microsoft.AspNetCore.Hosting.Diagnostics[2] + Request finished HTTP/1.1 GET http://localhost/test/throw-exception - 400 - application/json;+charset=utf-8 22.0381ms +info: Microsoft.AspNetCore.Hosting.Diagnostics[1] + Request starting HTTP/1.1 GET http://localhost/test/collection-empty - - - +info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0] + Executing endpoint 'ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.GetCollectionEmpty (ManagedCode.Communication.Tests)' +info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[102] + Route matched with {action = "GetCollectionEmpty", controller = "Test"}. Executing controller action with signature ManagedCode.Communication.CollectionResultT.CollectionResult`1[ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestModel] GetCollectionEmpty() on controller ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController (ManagedCode.Communication.Tests). +info: Microsoft.AspNetCore.Mvc.Infrastructure.ObjectResultExecutor[1] + Executing ObjectResult, writing value of type 'ManagedCode.Communication.CollectionResultT.CollectionResult`1[[ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestModel, ManagedCode.Communication.Tests, Version=9.6.2.0, Culture=neutral, PublicKeyToken=null]]'. +info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[105] + Executed action ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.GetCollectionEmpty (ManagedCode.Communication.Tests) in 0.485ms +info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1] + Executed endpoint 'ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.GetCollectionEmpty (ManagedCode.Communication.Tests)' +info: Microsoft.AspNetCore.Hosting.Diagnostics[2] + Request finished HTTP/1.1 GET http://localhost/test/collection-empty - 200 - application/json;+charset=utf-8 2.1438ms +info: Microsoft.AspNetCore.Hosting.Diagnostics[1] + Request starting HTTP/1.1 POST http://localhost/test/validate - application/json;+charset=utf-8 55 +info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0] + Executing endpoint 'ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.Validate (ManagedCode.Communication.Tests)' +info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[102] + Route matched with {action = "Validate", controller = "Test"}. Executing controller action with signature Microsoft.AspNetCore.Mvc.ActionResult`1[System.String] Validate(ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestValidationModel) on controller ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController (ManagedCode.Communication.Tests). +info: Microsoft.AspNetCore.Mvc.Infrastructure.ObjectResultExecutor[1] + Executing OkObjectResult, writing value of type 'System.String'. +info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[105] + Executed action ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.Validate (ManagedCode.Communication.Tests) in 2.3696ms +info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1] + Executed endpoint 'ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.Validate (ManagedCode.Communication.Tests)' +info: Microsoft.AspNetCore.Hosting.Diagnostics[2] + Request finished HTTP/1.1 POST http://localhost/test/validate - 200 - text/plain;+charset=utf-8 3.0451ms +info: Microsoft.AspNetCore.Hosting.Diagnostics[1] + Request starting HTTP/1.1 GET http://localhost/test/result-notfound - - - +info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0] + Executing endpoint 'ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.GetResultNotFoundTest (ManagedCode.Communication.Tests)' +info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[102] + Route matched with {action = "GetResultNotFoundTest", controller = "Test"}. Executing controller action with signature ManagedCode.Communication.Result`1[System.String] GetResultNotFoundTest() on controller ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController (ManagedCode.Communication.Tests). +info: Microsoft.AspNetCore.Mvc.Infrastructure.ObjectResultExecutor[1] + Executing ObjectResult, writing value of type 'ManagedCode.Communication.Result`1[[System.String, System.Private.CoreLib, Version=9.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]]'. +info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[105] + Executed action ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.GetResultNotFoundTest (ManagedCode.Communication.Tests) in 9.8268ms +info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1] + Executed endpoint 'ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.GetResultNotFoundTest (ManagedCode.Communication.Tests)' +info: Microsoft.AspNetCore.Hosting.Diagnostics[2] + Request finished HTTP/1.1 GET http://localhost/test/result-notfound - 404 - application/json;+charset=utf-8 11.9658ms +info: Microsoft.AspNetCore.Hosting.Diagnostics[1] + Request starting HTTP/1.1 GET http://localhost/test/result-failure - - - +info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0] + Executing endpoint 'ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.GetResultFailure (ManagedCode.Communication.Tests)' +info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[102] + Route matched with {action = "GetResultFailure", controller = "Test"}. Executing controller action with signature ManagedCode.Communication.Result`1[System.String] GetResultFailure() on controller ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController (ManagedCode.Communication.Tests). +info: Microsoft.AspNetCore.Mvc.Infrastructure.ObjectResultExecutor[1] + Executing ObjectResult, writing value of type 'ManagedCode.Communication.Result`1[[System.String, System.Private.CoreLib, Version=9.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]]'. +info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[105] + Executed action ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.GetResultFailure (ManagedCode.Communication.Tests) in 0.9254ms +info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1] + Executed endpoint 'ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.GetResultFailure (ManagedCode.Communication.Tests)' +info: Microsoft.AspNetCore.Hosting.Diagnostics[2] + Request finished HTTP/1.1 GET http://localhost/test/result-failure - 400 - application/json;+charset=utf-8 2.7400ms +info: Microsoft.AspNetCore.Hosting.Diagnostics[1] + Request starting HTTP/1.1 GET http://localhost/test/result-success - - - +info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0] + Executing endpoint 'ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.GetResultSuccessWithValue (ManagedCode.Communication.Tests)' +info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[102] + Route matched with {action = "GetResultSuccessWithValue", controller = "Test"}. Executing controller action with signature ManagedCode.Communication.Result`1[ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestModel] GetResultSuccessWithValue() on controller ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController (ManagedCode.Communication.Tests). +info: Microsoft.AspNetCore.Mvc.Infrastructure.ObjectResultExecutor[1] + Executing ObjectResult, writing value of type 'ManagedCode.Communication.Result`1[[ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestModel, ManagedCode.Communication.Tests, Version=9.6.2.0, Culture=neutral, PublicKeyToken=null]]'. +info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[105] + Executed action ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.GetResultSuccessWithValue (ManagedCode.Communication.Tests) in 3.0901ms +info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1] + Executed endpoint 'ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.GetResultSuccessWithValue (ManagedCode.Communication.Tests)' +info: Microsoft.AspNetCore.Hosting.Diagnostics[2] + Request finished HTTP/1.1 GET http://localhost/test/result-success - 200 - application/json;+charset=utf-8 4.6218ms +info: Microsoft.AspNetCore.Hosting.Diagnostics[1] + Request starting HTTP/1.1 GET http://localhost/test/enum-error - - - +info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0] + Executing endpoint 'ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.GetEnumError (ManagedCode.Communication.Tests)' +info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[102] + Route matched with {action = "GetEnumError", controller = "Test"}. Executing controller action with signature ManagedCode.Communication.Result`1[System.String] GetEnumError() on controller ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController (ManagedCode.Communication.Tests). +info: Microsoft.AspNetCore.Mvc.Infrastructure.ObjectResultExecutor[1] + Executing ObjectResult, writing value of type 'ManagedCode.Communication.Result`1[[System.String, System.Private.CoreLib, Version=9.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]]'. +info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[105] + Executed action ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.GetEnumError (ManagedCode.Communication.Tests) in 0.6791ms +info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1] + Executed endpoint 'ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.GetEnumError (ManagedCode.Communication.Tests)' +info: Microsoft.AspNetCore.Hosting.Diagnostics[2] + Request finished HTTP/1.1 GET http://localhost/test/enum-error - 400 - application/json;+charset=utf-8 2.1363ms +info: Microsoft.AspNetCore.Hosting.Diagnostics[1] + Request starting HTTP/1.1 GET http://localhost/test/result-success - - - +info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0] + Executing endpoint 'ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.GetResultSuccessWithValue (ManagedCode.Communication.Tests)' +info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[102] + Route matched with {action = "GetResultSuccessWithValue", controller = "Test"}. Executing controller action with signature ManagedCode.Communication.Result`1[ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestModel] GetResultSuccessWithValue() on controller ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController (ManagedCode.Communication.Tests). +info: Microsoft.AspNetCore.Mvc.Infrastructure.ObjectResultExecutor[1] + Executing ObjectResult, writing value of type 'ManagedCode.Communication.Result`1[[ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestModel, ManagedCode.Communication.Tests, Version=9.6.2.0, Culture=neutral, PublicKeyToken=null]]'. +info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[105] + Executed action ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.GetResultSuccessWithValue (ManagedCode.Communication.Tests) in 0.7359ms +info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1] + Executed endpoint 'ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.GetResultSuccessWithValue (ManagedCode.Communication.Tests)' +info: Microsoft.AspNetCore.Hosting.Diagnostics[2] + Request finished HTTP/1.1 GET http://localhost/test/result-success - 200 - application/json;+charset=utf-8 0.9482ms +info: Microsoft.AspNetCore.Hosting.Diagnostics[1] + Request starting HTTP/1.1 GET http://localhost/test/test2 - - - +info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0] + Executing endpoint 'ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.Test2 (ManagedCode.Communication.Tests)' +info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[102] + Route matched with {action = "Test2", controller = "Test"}. Executing controller action with signature Microsoft.AspNetCore.Mvc.ActionResult`1[System.String] Test2() on controller ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController (ManagedCode.Communication.Tests). +fail: ManagedCode.Communication.AspNetCore.Filters.CommunicationExceptionFilter[0] + Unhandled exception in Test.ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.Test2 (ManagedCode.Communication.Tests) + System.IO.InvalidDataException: InvalidDataException + at ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.Test2() in /_/ManagedCode.Communication.Tests/Common/TestApp/Controllers/TestController.cs:line 24 + at lambda_method119(Closure, Object, Object[]) + at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.SyncObjectResultExecutor.Execute(ActionContext actionContext, IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments) + at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeActionMethodAsync() + at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted) + at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeNextActionFilterAsync() + --- End of stack trace from previous location --- + at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context) + at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted) + at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeInnerFilterAsync() + --- End of stack trace from previous location --- + at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextExceptionFilterAsync>g__Awaited|26_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted) +info: ManagedCode.Communication.AspNetCore.Filters.CommunicationExceptionFilter[0] + Exception handled by CommunicationExceptionFilter for Test.ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.Test2 (ManagedCode.Communication.Tests) +info: Microsoft.AspNetCore.Mvc.Infrastructure.ObjectResultExecutor[1] + Executing ObjectResult, writing value of type 'ManagedCode.Communication.Result'. +info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[105] + Executed action ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.Test2 (ManagedCode.Communication.Tests) in 5.2306ms +info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1] + Executed endpoint 'ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.Test2 (ManagedCode.Communication.Tests)' +info: Microsoft.AspNetCore.Hosting.Diagnostics[2] + Request finished HTTP/1.1 GET http://localhost/test/test2 - 409 - application/json;+charset=utf-8 8.2771ms +info: Microsoft.AspNetCore.Hosting.Diagnostics[1] + Request starting HTTP/1.1 GET http://localhost/test/result-unauthorized - - - +info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0] + Executing endpoint 'ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.GetResultUnauthorized (ManagedCode.Communication.Tests)' +info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[102] + Route matched with {action = "GetResultUnauthorized", controller = "Test"}. Executing controller action with signature ManagedCode.Communication.Result GetResultUnauthorized() on controller ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController (ManagedCode.Communication.Tests). +info: Microsoft.AspNetCore.Mvc.Infrastructure.ObjectResultExecutor[1] + Executing ObjectResult, writing value of type 'ManagedCode.Communication.Result'. +info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[105] + Executed action ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.GetResultUnauthorized (ManagedCode.Communication.Tests) in 2.9514ms +info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1] + Executed endpoint 'ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.GetResultUnauthorized (ManagedCode.Communication.Tests)' +info: Microsoft.AspNetCore.Hosting.Diagnostics[2] + Request finished HTTP/1.1 GET http://localhost/test/result-unauthorized - 401 - application/json;+charset=utf-8 7.3584ms +info: Microsoft.AspNetCore.Hosting.Diagnostics[1] + Request starting HTTP/2 POST http://localhost/TestHub/negotiate?negotiateVersion=1 - - - +info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0] + Executing endpoint 'TestHub/negotiate' +info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1] + Executed endpoint 'TestHub/negotiate' +info: Microsoft.AspNetCore.Hosting.Diagnostics[2] + Request finished HTTP/2 POST http://localhost/TestHub/negotiate?negotiateVersion=1 - 200 316 application/json 14.9025ms +info: Microsoft.AspNetCore.Hosting.Diagnostics[1] + Request starting HTTP/2 POST http://localhost/TestHub/negotiate?negotiateVersion=1 - - - +info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0] + Executing endpoint 'TestHub/negotiate' +info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1] + Executed endpoint 'TestHub/negotiate' +info: Microsoft.AspNetCore.Hosting.Diagnostics[2] + Request finished HTTP/2 POST http://localhost/TestHub/negotiate?negotiateVersion=1 - 200 316 application/json 0.5284ms +info: Microsoft.AspNetCore.Hosting.Diagnostics[1] + Request starting HTTP/2 GET http://localhost/TestHub?id=_4PuJqwpTJLYR6v7gM6-ZQ - - - +info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0] + Executing endpoint 'TestHub' +info: Microsoft.AspNetCore.Hosting.Diagnostics[1] + Request starting HTTP/2 POST http://localhost/TestHub?id=_4PuJqwpTJLYR6v7gM6-ZQ - - 32 +info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0] + Executing endpoint 'TestHub' +info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1] + Executed endpoint 'TestHub' +info: Microsoft.AspNetCore.Hosting.Diagnostics[2] + Request finished HTTP/2 POST http://localhost/TestHub?id=_4PuJqwpTJLYR6v7gM6-ZQ - 200 - text/plain 1.8656ms +info: Microsoft.AspNetCore.Hosting.Diagnostics[1] + Request starting HTTP/2 POST http://localhost/TestHub?id=_4PuJqwpTJLYR6v7gM6-ZQ - - 11 +info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0] + Executing endpoint 'TestHub' +info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1] + Executed endpoint 'TestHub' +info: Microsoft.AspNetCore.Hosting.Diagnostics[2] + Request finished HTTP/2 POST http://localhost/TestHub?id=_4PuJqwpTJLYR6v7gM6-ZQ - 200 - text/plain 0.2666ms +info: Microsoft.AspNetCore.Hosting.Diagnostics[1] + Request starting HTTP/2 POST http://localhost/TestHub?id=_4PuJqwpTJLYR6v7gM6-ZQ - - 63 +info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0] + Executing endpoint 'TestHub' +info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1] + Executed endpoint 'TestHub' +info: Microsoft.AspNetCore.Hosting.Diagnostics[2] + Request finished HTTP/2 POST http://localhost/TestHub?id=_4PuJqwpTJLYR6v7gM6-ZQ - 200 - text/plain 0.2351ms +Current value: 10 +info: Microsoft.AspNetCore.Hosting.Diagnostics[1] + Request starting HTTP/1.1 GET http://localhost/test/result-fail - - - +info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0] + Executing endpoint 'ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.GetResultFail (ManagedCode.Communication.Tests)' +info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[102] + Route matched with {action = "GetResultFail", controller = "Test"}. Executing controller action with signature ManagedCode.Communication.Result GetResultFail() on controller ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController (ManagedCode.Communication.Tests). +info: Microsoft.AspNetCore.Mvc.Infrastructure.ObjectResultExecutor[1] + Executing ObjectResult, writing value of type 'ManagedCode.Communication.Result'. +info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[105] + Executed action ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.GetResultFail (ManagedCode.Communication.Tests) in 1.5711ms +info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1] + Executed endpoint 'ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.GetResultFail (ManagedCode.Communication.Tests)' +info: Microsoft.AspNetCore.Hosting.Diagnostics[2] + Request finished HTTP/1.1 GET http://localhost/test/result-fail - 400 - application/json;+charset=utf-8 3.3251ms +info: Microsoft.AspNetCore.Hosting.Diagnostics[1] + Request starting HTTP/1.1 GET http://localhost/test/result-not-found - - - +info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0] + Executing endpoint 'ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.GetResultNotFound (ManagedCode.Communication.Tests)' +info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[102] + Route matched with {action = "GetResultNotFound", controller = "Test"}. Executing controller action with signature ManagedCode.Communication.Result`1[System.String] GetResultNotFound() on controller ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController (ManagedCode.Communication.Tests). +info: Microsoft.AspNetCore.Mvc.Infrastructure.ObjectResultExecutor[1] + Executing ObjectResult, writing value of type 'ManagedCode.Communication.Result`1[[System.String, System.Private.CoreLib, Version=9.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]]'. +info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[105] + Executed action ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.GetResultNotFound (ManagedCode.Communication.Tests) in 1.3899ms +info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1] + Executed endpoint 'ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.GetResultNotFound (ManagedCode.Communication.Tests)' +info: Microsoft.AspNetCore.Hosting.Diagnostics[2] + Request finished HTTP/1.1 GET http://localhost/test/result-not-found - 404 - application/json;+charset=utf-8 2.9295ms +info: Microsoft.AspNetCore.Hosting.Diagnostics[1] + Request starting HTTP/1.1 GET http://localhost/test/test1 - - - +info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0] + Executing endpoint 'ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.Test1 (ManagedCode.Communication.Tests)' +info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[102] + Route matched with {action = "Test1", controller = "Test"}. Executing controller action with signature Microsoft.AspNetCore.Mvc.ActionResult`1[System.String] Test1() on controller ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController (ManagedCode.Communication.Tests). +fail: ManagedCode.Communication.AspNetCore.Filters.CommunicationExceptionFilter[0] + Unhandled exception in Test.ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.Test1 (ManagedCode.Communication.Tests) + System.ComponentModel.DataAnnotations.ValidationException: ValidationException + at ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.Test1() in /_/ManagedCode.Communication.Tests/Common/TestApp/Controllers/TestController.cs:line 18 + at lambda_method131(Closure, Object, Object[]) + at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.SyncObjectResultExecutor.Execute(ActionContext actionContext, IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments) + at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeActionMethodAsync() + at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted) + at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeNextActionFilterAsync() + --- End of stack trace from previous location --- + at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context) + at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted) + at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeInnerFilterAsync() + --- End of stack trace from previous location --- + at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextExceptionFilterAsync>g__Awaited|26_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted) +info: ManagedCode.Communication.AspNetCore.Filters.CommunicationExceptionFilter[0] + Exception handled by CommunicationExceptionFilter for Test.ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.Test1 (ManagedCode.Communication.Tests) +info: Microsoft.AspNetCore.Mvc.Infrastructure.ObjectResultExecutor[1] + Executing ObjectResult, writing value of type 'ManagedCode.Communication.Result'. +info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[105] + Executed action ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.Test1 (ManagedCode.Communication.Tests) in 1.2873ms +info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1] + Executed endpoint 'ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.Test1 (ManagedCode.Communication.Tests)' +info: Microsoft.AspNetCore.Hosting.Diagnostics[2] + Request finished HTTP/1.1 GET http://localhost/test/test1 - 500 - application/json;+charset=utf-8 2.1952ms +info: Microsoft.AspNetCore.Hosting.Diagnostics[1] + Request starting HTTP/2 POST http://localhost/TestHub/negotiate?negotiateVersion=1 - - - +info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0] + Executing endpoint 'TestHub/negotiate' +info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1] + Executed endpoint 'TestHub/negotiate' +info: Microsoft.AspNetCore.Hosting.Diagnostics[2] + Request finished HTTP/2 POST http://localhost/TestHub/negotiate?negotiateVersion=1 - 200 316 application/json 1.4226ms +info: Microsoft.AspNetCore.Hosting.Diagnostics[1] + Request starting HTTP/2 POST http://localhost/TestHub/negotiate?negotiateVersion=1 - - - +info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0] + Executing endpoint 'TestHub/negotiate' +info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1] + Executed endpoint 'TestHub/negotiate' +info: Microsoft.AspNetCore.Hosting.Diagnostics[2] + Request finished HTTP/2 POST http://localhost/TestHub/negotiate?negotiateVersion=1 - 200 316 application/json 1.1951ms +info: Microsoft.AspNetCore.Hosting.Diagnostics[1] + Request starting HTTP/2 GET http://localhost/TestHub?id=VT0JM46vYtZli6P_2sa7qA - - - +info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0] + Executing endpoint 'TestHub' +info: Microsoft.AspNetCore.Hosting.Diagnostics[1] + Request starting HTTP/2 POST http://localhost/TestHub?id=VT0JM46vYtZli6P_2sa7qA - - 32 +info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0] + Executing endpoint 'TestHub' +info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1] + Executed endpoint 'TestHub' +info: Microsoft.AspNetCore.Hosting.Diagnostics[2] + Request finished HTTP/2 POST http://localhost/TestHub?id=VT0JM46vYtZli6P_2sa7qA - 200 - text/plain 0.7343ms +info: Microsoft.AspNetCore.Hosting.Diagnostics[1] + Request starting HTTP/2 POST http://localhost/TestHub?id=VT0JM46vYtZli6P_2sa7qA - - 11 +info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0] + Executing endpoint 'TestHub' +info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1] + Executed endpoint 'TestHub' +info: Microsoft.AspNetCore.Hosting.Diagnostics[2] + Request finished HTTP/2 POST http://localhost/TestHub?id=VT0JM46vYtZli6P_2sa7qA - 200 - text/plain 0.3475ms +info: Microsoft.AspNetCore.Hosting.Diagnostics[1] + Request starting HTTP/2 POST http://localhost/TestHub?id=VT0JM46vYtZli6P_2sa7qA - - 62 +info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0] + Executing endpoint 'TestHub' +info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1] + Executed endpoint 'TestHub' +info: Microsoft.AspNetCore.Hosting.Diagnostics[2] + Request finished HTTP/2 POST http://localhost/TestHub?id=VT0JM46vYtZli6P_2sa7qA - 200 - text/plain 0.1432ms +fail: ManagedCode.Communication.AspNetCore.Filters.CommunicationHubExceptionFilter[0] + Unhandled exception in hub method TestHub.Throw + System.IO.InvalidDataException: InvalidDataException + at ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestHub.Throw() in /_/ManagedCode.Communication.Tests/Common/TestApp/Controllers/TestHub.cs:line 17 + at lambda_method85(Closure, Object, Object[]) + at Microsoft.AspNetCore.SignalR.Internal.DefaultHubDispatcher`1.ExecuteMethod(ObjectMethodExecutor methodExecutor, Hub hub, Object[] arguments) + at ManagedCode.Communication.AspNetCore.Filters.CommunicationHubExceptionFilter.InvokeMethodAsync(HubInvocationContext invocationContext, Func`2 next) in /_/ManagedCode.Communication.AspNetCore/SignalR/Filters/CommunicationHubExceptionFilter.cs:line 15 +info: Microsoft.AspNetCore.Hosting.Diagnostics[1] + Request starting HTTP/1.1 GET http://localhost/test/test3 - - - +info: Microsoft.AspNetCore.Authorization.DefaultAuthorizationService[2] + Authorization failed. These requirements were not met: + DenyAnonymousAuthorizationRequirement: Requires an authenticated user. +info: ManagedCode.Communication.Tests.Common.TestApp.TestAuthenticationHandler[12] + AuthenticationScheme: Test was challenged. +info: Microsoft.AspNetCore.Hosting.Diagnostics[2] + Request finished HTTP/1.1 GET http://localhost/test/test3 - 401 - - 4.2841ms +info: Microsoft.AspNetCore.Hosting.Diagnostics[1] + Request starting HTTP/1.1 GET http://localhost/test/result-forbidden - - - +info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0] + Executing endpoint 'ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.GetResultForbidden (ManagedCode.Communication.Tests)' +info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[102] + Route matched with {action = "GetResultForbidden", controller = "Test"}. Executing controller action with signature ManagedCode.Communication.Result GetResultForbidden() on controller ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController (ManagedCode.Communication.Tests). +info: Microsoft.AspNetCore.Mvc.Infrastructure.ObjectResultExecutor[1] + Executing ObjectResult, writing value of type 'ManagedCode.Communication.Result'. +info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[105] + Executed action ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.GetResultForbidden (ManagedCode.Communication.Tests) in 0.3604ms +info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1] + Executed endpoint 'ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.GetResultForbidden (ManagedCode.Communication.Tests)' +info: Microsoft.AspNetCore.Hosting.Diagnostics[2] + Request finished HTTP/1.1 GET http://localhost/test/result-forbidden - 403 - application/json;+charset=utf-8 2.2981ms +info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1] + Executed endpoint 'TestHub' +info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1] + Executed endpoint 'TestHub' +info: Microsoft.AspNetCore.Hosting.Diagnostics[2] + Request finished HTTP/2 GET http://localhost/TestHub?id=VT0JM46vYtZli6P_2sa7qA - 200 - text/event-stream 163.8080ms +info: Microsoft.AspNetCore.Hosting.Diagnostics[2] + Request finished HTTP/2 GET http://localhost/TestHub?id=_4PuJqwpTJLYR6v7gM6-ZQ - 200 - text/event-stream 534.1254ms +[xUnit.net 00:00:03.60] Finished: ManagedCode.Communication.Tests + + + + \ No newline at end of file diff --git a/dotnet-install.sh b/dotnet-install.sh new file mode 100755 index 0000000..034d2df --- /dev/null +++ b/dotnet-install.sh @@ -0,0 +1,1888 @@ +#!/usr/bin/env bash +# Copyright (c) .NET Foundation and contributors. All rights reserved. +# Licensed under the MIT license. See LICENSE file in the project root for full license information. +# + +# Stop script on NZEC +set -e +# Stop script if unbound variable found (use ${var:-} if intentional) +set -u +# By default cmd1 | cmd2 returns exit code of cmd2 regardless of cmd1 success +# This is causing it to fail +set -o pipefail + +# Use in the the functions: eval $invocation +invocation='say_verbose "Calling: ${yellow:-}${FUNCNAME[0]} ${green:-}$*${normal:-}"' + +# standard output may be used as a return value in the functions +# we need a way to write text on the screen in the functions so that +# it won't interfere with the return value. +# Exposing stream 3 as a pipe to standard output of the script itself +exec 3>&1 + +# Setup some colors to use. These need to work in fairly limited shells, like the Ubuntu Docker container where there are only 8 colors. +# See if stdout is a terminal +if [ -t 1 ] && command -v tput > /dev/null; then + # see if it supports colors + ncolors=$(tput colors || echo 0) + if [ -n "$ncolors" ] && [ $ncolors -ge 8 ]; then + bold="$(tput bold || echo)" + normal="$(tput sgr0 || echo)" + black="$(tput setaf 0 || echo)" + red="$(tput setaf 1 || echo)" + green="$(tput setaf 2 || echo)" + yellow="$(tput setaf 3 || echo)" + blue="$(tput setaf 4 || echo)" + magenta="$(tput setaf 5 || echo)" + cyan="$(tput setaf 6 || echo)" + white="$(tput setaf 7 || echo)" + fi +fi + +say_warning() { + printf "%b\n" "${yellow:-}dotnet_install: Warning: $1${normal:-}" >&3 +} + +say_err() { + printf "%b\n" "${red:-}dotnet_install: Error: $1${normal:-}" >&2 +} + +say() { + # using stream 3 (defined in the beginning) to not interfere with stdout of functions + # which may be used as return value + printf "%b\n" "${cyan:-}dotnet-install:${normal:-} $1" >&3 +} + +say_verbose() { + if [ "$verbose" = true ]; then + say "$1" + fi +} + +# This platform list is finite - if the SDK/Runtime has supported Linux distribution-specific assets, +# then and only then should the Linux distribution appear in this list. +# Adding a Linux distribution to this list does not imply distribution-specific support. +get_legacy_os_name_from_platform() { + eval $invocation + + platform="$1" + case "$platform" in + "centos.7") + echo "centos" + return 0 + ;; + "debian.8") + echo "debian" + return 0 + ;; + "debian.9") + echo "debian.9" + return 0 + ;; + "fedora.23") + echo "fedora.23" + return 0 + ;; + "fedora.24") + echo "fedora.24" + return 0 + ;; + "fedora.27") + echo "fedora.27" + return 0 + ;; + "fedora.28") + echo "fedora.28" + return 0 + ;; + "opensuse.13.2") + echo "opensuse.13.2" + return 0 + ;; + "opensuse.42.1") + echo "opensuse.42.1" + return 0 + ;; + "opensuse.42.3") + echo "opensuse.42.3" + return 0 + ;; + "rhel.7"*) + echo "rhel" + return 0 + ;; + "ubuntu.14.04") + echo "ubuntu" + return 0 + ;; + "ubuntu.16.04") + echo "ubuntu.16.04" + return 0 + ;; + "ubuntu.16.10") + echo "ubuntu.16.10" + return 0 + ;; + "ubuntu.18.04") + echo "ubuntu.18.04" + return 0 + ;; + "alpine.3.4.3") + echo "alpine" + return 0 + ;; + esac + return 1 +} + +get_legacy_os_name() { + eval $invocation + + local uname=$(uname) + if [ "$uname" = "Darwin" ]; then + echo "osx" + return 0 + elif [ -n "$runtime_id" ]; then + echo $(get_legacy_os_name_from_platform "${runtime_id%-*}" || echo "${runtime_id%-*}") + return 0 + else + if [ -e /etc/os-release ]; then + . /etc/os-release + os=$(get_legacy_os_name_from_platform "$ID${VERSION_ID:+.${VERSION_ID}}" || echo "") + if [ -n "$os" ]; then + echo "$os" + return 0 + fi + fi + fi + + say_verbose "Distribution specific OS name and version could not be detected: UName = $uname" + return 1 +} + +get_linux_platform_name() { + eval $invocation + + if [ -n "$runtime_id" ]; then + echo "${runtime_id%-*}" + return 0 + else + if [ -e /etc/os-release ]; then + . /etc/os-release + echo "$ID${VERSION_ID:+.${VERSION_ID}}" + return 0 + elif [ -e /etc/redhat-release ]; then + local redhatRelease=$(&1 || true) | grep -q musl +} + +get_current_os_name() { + eval $invocation + + local uname=$(uname) + if [ "$uname" = "Darwin" ]; then + echo "osx" + return 0 + elif [ "$uname" = "FreeBSD" ]; then + echo "freebsd" + return 0 + elif [ "$uname" = "Linux" ]; then + local linux_platform_name="" + linux_platform_name="$(get_linux_platform_name)" || true + + if [ "$linux_platform_name" = "rhel.6" ]; then + echo $linux_platform_name + return 0 + elif is_musl_based_distro; then + echo "linux-musl" + return 0 + elif [ "$linux_platform_name" = "linux-musl" ]; then + echo "linux-musl" + return 0 + else + echo "linux" + return 0 + fi + fi + + say_err "OS name could not be detected: UName = $uname" + return 1 +} + +machine_has() { + eval $invocation + + command -v "$1" > /dev/null 2>&1 + return $? +} + +check_min_reqs() { + local hasMinimum=false + if machine_has "curl"; then + hasMinimum=true + elif machine_has "wget"; then + hasMinimum=true + fi + + if [ "$hasMinimum" = "false" ]; then + say_err "curl (recommended) or wget are required to download dotnet. Install missing prerequisite to proceed." + return 1 + fi + return 0 +} + +# args: +# input - $1 +to_lowercase() { + #eval $invocation + + echo "$1" | tr '[:upper:]' '[:lower:]' + return 0 +} + +# args: +# input - $1 +remove_trailing_slash() { + #eval $invocation + + local input="${1:-}" + echo "${input%/}" + return 0 +} + +# args: +# input - $1 +remove_beginning_slash() { + #eval $invocation + + local input="${1:-}" + echo "${input#/}" + return 0 +} + +# args: +# root_path - $1 +# child_path - $2 - this parameter can be empty +combine_paths() { + eval $invocation + + # TODO: Consider making it work with any number of paths. For now: + if [ ! -z "${3:-}" ]; then + say_err "combine_paths: Function takes two parameters." + return 1 + fi + + local root_path="$(remove_trailing_slash "$1")" + local child_path="$(remove_beginning_slash "${2:-}")" + say_verbose "combine_paths: root_path=$root_path" + say_verbose "combine_paths: child_path=$child_path" + echo "$root_path/$child_path" + return 0 +} + +get_machine_architecture() { + eval $invocation + + if command -v uname > /dev/null; then + CPUName=$(uname -m) + case $CPUName in + armv1*|armv2*|armv3*|armv4*|armv5*|armv6*) + echo "armv6-or-below" + return 0 + ;; + armv*l) + echo "arm" + return 0 + ;; + aarch64|arm64) + if [ "$(getconf LONG_BIT)" -lt 64 ]; then + # This is 32-bit OS running on 64-bit CPU (for example Raspberry Pi OS) + echo "arm" + return 0 + fi + echo "arm64" + return 0 + ;; + s390x) + echo "s390x" + return 0 + ;; + ppc64le) + echo "ppc64le" + return 0 + ;; + loongarch64) + echo "loongarch64" + return 0 + ;; + riscv64) + echo "riscv64" + return 0 + ;; + powerpc|ppc) + echo "ppc" + return 0 + ;; + esac + fi + + # Always default to 'x64' + echo "x64" + return 0 +} + +# args: +# architecture - $1 +get_normalized_architecture_from_architecture() { + eval $invocation + + local architecture="$(to_lowercase "$1")" + + if [[ $architecture == \ ]]; then + machine_architecture="$(get_machine_architecture)" + if [[ "$machine_architecture" == "armv6-or-below" ]]; then + say_err "Architecture \`$machine_architecture\` not supported. If you think this is a bug, report it at https://github.com/dotnet/install-scripts/issues" + return 1 + fi + + echo $machine_architecture + return 0 + fi + + case "$architecture" in + amd64|x64) + echo "x64" + return 0 + ;; + arm) + echo "arm" + return 0 + ;; + arm64) + echo "arm64" + return 0 + ;; + s390x) + echo "s390x" + return 0 + ;; + ppc64le) + echo "ppc64le" + return 0 + ;; + loongarch64) + echo "loongarch64" + return 0 + ;; + esac + + say_err "Architecture \`$architecture\` not supported. If you think this is a bug, report it at https://github.com/dotnet/install-scripts/issues" + return 1 +} + +# args: +# version - $1 +# channel - $2 +# architecture - $3 +get_normalized_architecture_for_specific_sdk_version() { + eval $invocation + + local is_version_support_arm64="$(is_arm64_supported "$1")" + local is_channel_support_arm64="$(is_arm64_supported "$2")" + local architecture="$3"; + local osname="$(get_current_os_name)" + + if [ "$osname" == "osx" ] && [ "$architecture" == "arm64" ] && { [ "$is_version_support_arm64" = false ] || [ "$is_channel_support_arm64" = false ]; }; then + #check if rosetta is installed + if [ "$(/usr/bin/pgrep oahd >/dev/null 2>&1;echo $?)" -eq 0 ]; then + say_verbose "Changing user architecture from '$architecture' to 'x64' because .NET SDKs prior to version 6.0 do not support arm64." + echo "x64" + return 0; + else + say_err "Architecture \`$architecture\` is not supported for .NET SDK version \`$version\`. Please install Rosetta to allow emulation of the \`$architecture\` .NET SDK on this platform" + return 1 + fi + fi + + echo "$architecture" + return 0 +} + +# args: +# version or channel - $1 +is_arm64_supported() { + # Extract the major version by splitting on the dot + major_version="${1%%.*}" + + # Check if the major version is a valid number and less than 6 + case "$major_version" in + [0-9]*) + if [ "$major_version" -lt 6 ]; then + echo false + return 0 + fi + ;; + esac + + echo true + return 0 +} + +# args: +# user_defined_os - $1 +get_normalized_os() { + eval $invocation + + local osname="$(to_lowercase "$1")" + if [ ! -z "$osname" ]; then + case "$osname" in + osx | freebsd | rhel.6 | linux-musl | linux) + echo "$osname" + return 0 + ;; + macos) + osname='osx' + echo "$osname" + return 0 + ;; + *) + say_err "'$user_defined_os' is not a supported value for --os option, supported values are: osx, macos, linux, linux-musl, freebsd, rhel.6. If you think this is a bug, report it at https://github.com/dotnet/install-scripts/issues." + return 1 + ;; + esac + else + osname="$(get_current_os_name)" || return 1 + fi + echo "$osname" + return 0 +} + +# args: +# quality - $1 +get_normalized_quality() { + eval $invocation + + local quality="$(to_lowercase "$1")" + if [ ! -z "$quality" ]; then + case "$quality" in + daily | preview) + echo "$quality" + return 0 + ;; + ga) + #ga quality is available without specifying quality, so normalizing it to empty + return 0 + ;; + *) + say_err "'$quality' is not a supported value for --quality option. Supported values are: daily, preview, ga. If you think this is a bug, report it at https://github.com/dotnet/install-scripts/issues." + return 1 + ;; + esac + fi + return 0 +} + +# args: +# channel - $1 +get_normalized_channel() { + eval $invocation + + local channel="$(to_lowercase "$1")" + + if [[ $channel == current ]]; then + say_warning 'Value "Current" is deprecated for -Channel option. Use "STS" instead.' + fi + + if [[ $channel == release/* ]]; then + say_warning 'Using branch name with -Channel option is no longer supported with newer releases. Use -Quality option with a channel in X.Y format instead.'; + fi + + if [ ! -z "$channel" ]; then + case "$channel" in + lts) + echo "LTS" + return 0 + ;; + sts) + echo "STS" + return 0 + ;; + current) + echo "STS" + return 0 + ;; + *) + echo "$channel" + return 0 + ;; + esac + fi + + return 0 +} + +# args: +# runtime - $1 +get_normalized_product() { + eval $invocation + + local product="" + local runtime="$(to_lowercase "$1")" + if [[ "$runtime" == "dotnet" ]]; then + product="dotnet-runtime" + elif [[ "$runtime" == "aspnetcore" ]]; then + product="aspnetcore-runtime" + elif [ -z "$runtime" ]; then + product="dotnet-sdk" + fi + echo "$product" + return 0 +} + +# The version text returned from the feeds is a 1-line or 2-line string: +# For the SDK and the dotnet runtime (2 lines): +# Line 1: # commit_hash +# Line 2: # 4-part version +# For the aspnetcore runtime (1 line): +# Line 1: # 4-part version + +# args: +# version_text - stdin +get_version_from_latestversion_file_content() { + eval $invocation + + cat | tail -n 1 | sed 's/\r$//' + return 0 +} + +# args: +# install_root - $1 +# relative_path_to_package - $2 +# specific_version - $3 +is_dotnet_package_installed() { + eval $invocation + + local install_root="$1" + local relative_path_to_package="$2" + local specific_version="${3//[$'\t\r\n']}" + + local dotnet_package_path="$(combine_paths "$(combine_paths "$install_root" "$relative_path_to_package")" "$specific_version")" + say_verbose "is_dotnet_package_installed: dotnet_package_path=$dotnet_package_path" + + if [ -d "$dotnet_package_path" ]; then + return 0 + else + return 1 + fi +} + +# args: +# downloaded file - $1 +# remote_file_size - $2 +validate_remote_local_file_sizes() +{ + eval $invocation + + local downloaded_file="$1" + local remote_file_size="$2" + local file_size='' + + if [[ "$OSTYPE" == "linux-gnu"* ]]; then + file_size="$(stat -c '%s' "$downloaded_file")" + elif [[ "$OSTYPE" == "darwin"* ]]; then + # hardcode in order to avoid conflicts with GNU stat + file_size="$(/usr/bin/stat -f '%z' "$downloaded_file")" + fi + + if [ -n "$file_size" ]; then + say "Downloaded file size is $file_size bytes." + + if [ -n "$remote_file_size" ] && [ -n "$file_size" ]; then + if [ "$remote_file_size" -ne "$file_size" ]; then + say "The remote and local file sizes are not equal. The remote file size is $remote_file_size bytes and the local size is $file_size bytes. The local package may be corrupted." + else + say "The remote and local file sizes are equal." + fi + fi + + else + say "Either downloaded or local package size can not be measured. One of them may be corrupted." + fi +} + +# args: +# azure_feed - $1 +# channel - $2 +# normalized_architecture - $3 +get_version_from_latestversion_file() { + eval $invocation + + local azure_feed="$1" + local channel="$2" + local normalized_architecture="$3" + + local version_file_url=null + if [[ "$runtime" == "dotnet" ]]; then + version_file_url="$azure_feed/Runtime/$channel/latest.version" + elif [[ "$runtime" == "aspnetcore" ]]; then + version_file_url="$azure_feed/aspnetcore/Runtime/$channel/latest.version" + elif [ -z "$runtime" ]; then + version_file_url="$azure_feed/Sdk/$channel/latest.version" + else + say_err "Invalid value for \$runtime" + return 1 + fi + say_verbose "get_version_from_latestversion_file: latest url: $version_file_url" + + download "$version_file_url" || return $? + return 0 +} + +# args: +# json_file - $1 +parse_globaljson_file_for_version() { + eval $invocation + + local json_file="$1" + if [ ! -f "$json_file" ]; then + say_err "Unable to find \`$json_file\`" + return 1 + fi + + sdk_section=$(cat $json_file | tr -d "\r" | awk '/"sdk"/,/}/') + if [ -z "$sdk_section" ]; then + say_err "Unable to parse the SDK node in \`$json_file\`" + return 1 + fi + + sdk_list=$(echo $sdk_section | awk -F"[{}]" '{print $2}') + sdk_list=${sdk_list//[\" ]/} + sdk_list=${sdk_list//,/$'\n'} + + local version_info="" + while read -r line; do + IFS=: + while read -r key value; do + if [[ "$key" == "version" ]]; then + version_info=$value + fi + done <<< "$line" + done <<< "$sdk_list" + if [ -z "$version_info" ]; then + say_err "Unable to find the SDK:version node in \`$json_file\`" + return 1 + fi + + unset IFS; + echo "$version_info" + return 0 +} + +# args: +# azure_feed - $1 +# channel - $2 +# normalized_architecture - $3 +# version - $4 +# json_file - $5 +get_specific_version_from_version() { + eval $invocation + + local azure_feed="$1" + local channel="$2" + local normalized_architecture="$3" + local version="$(to_lowercase "$4")" + local json_file="$5" + + if [ -z "$json_file" ]; then + if [[ "$version" == "latest" ]]; then + local version_info + version_info="$(get_version_from_latestversion_file "$azure_feed" "$channel" "$normalized_architecture" false)" || return 1 + say_verbose "get_specific_version_from_version: version_info=$version_info" + echo "$version_info" | get_version_from_latestversion_file_content + return 0 + else + echo "$version" + return 0 + fi + else + local version_info + version_info="$(parse_globaljson_file_for_version "$json_file")" || return 1 + echo "$version_info" + return 0 + fi +} + +# args: +# azure_feed - $1 +# channel - $2 +# normalized_architecture - $3 +# specific_version - $4 +# normalized_os - $5 +construct_download_link() { + eval $invocation + + local azure_feed="$1" + local channel="$2" + local normalized_architecture="$3" + local specific_version="${4//[$'\t\r\n']}" + local specific_product_version="$(get_specific_product_version "$1" "$4")" + local osname="$5" + + local download_link=null + if [[ "$runtime" == "dotnet" ]]; then + download_link="$azure_feed/Runtime/$specific_version/dotnet-runtime-$specific_product_version-$osname-$normalized_architecture.tar.gz" + elif [[ "$runtime" == "aspnetcore" ]]; then + download_link="$azure_feed/aspnetcore/Runtime/$specific_version/aspnetcore-runtime-$specific_product_version-$osname-$normalized_architecture.tar.gz" + elif [ -z "$runtime" ]; then + download_link="$azure_feed/Sdk/$specific_version/dotnet-sdk-$specific_product_version-$osname-$normalized_architecture.tar.gz" + else + return 1 + fi + + echo "$download_link" + return 0 +} + +# args: +# azure_feed - $1 +# specific_version - $2 +# download link - $3 (optional) +get_specific_product_version() { + # If we find a 'productVersion.txt' at the root of any folder, we'll use its contents + # to resolve the version of what's in the folder, superseding the specified version. + # if 'productVersion.txt' is missing but download link is already available, product version will be taken from download link + eval $invocation + + local azure_feed="$1" + local specific_version="${2//[$'\t\r\n']}" + local package_download_link="" + if [ $# -gt 2 ]; then + local package_download_link="$3" + fi + local specific_product_version=null + + # Try to get the version number, using the productVersion.txt file located next to the installer file. + local download_links=($(get_specific_product_version_url "$azure_feed" "$specific_version" true "$package_download_link") + $(get_specific_product_version_url "$azure_feed" "$specific_version" false "$package_download_link")) + + for download_link in "${download_links[@]}" + do + say_verbose "Checking for the existence of $download_link" + + if machine_has "curl" + then + if ! specific_product_version=$(curl -s --fail "${download_link}${feed_credential}" 2>&1); then + continue + else + echo "${specific_product_version//[$'\t\r\n']}" + return 0 + fi + + elif machine_has "wget" + then + specific_product_version=$(wget -qO- "${download_link}${feed_credential}" 2>&1) + if [ $? = 0 ]; then + echo "${specific_product_version//[$'\t\r\n']}" + return 0 + fi + fi + done + + # Getting the version number with productVersion.txt has failed. Try parsing the download link for a version number. + say_verbose "Failed to get the version using productVersion.txt file. Download link will be parsed instead." + specific_product_version="$(get_product_specific_version_from_download_link "$package_download_link" "$specific_version")" + echo "${specific_product_version//[$'\t\r\n']}" + return 0 +} + +# args: +# azure_feed - $1 +# specific_version - $2 +# is_flattened - $3 +# download link - $4 (optional) +get_specific_product_version_url() { + eval $invocation + + local azure_feed="$1" + local specific_version="$2" + local is_flattened="$3" + local package_download_link="" + if [ $# -gt 3 ]; then + local package_download_link="$4" + fi + + local pvFileName="productVersion.txt" + if [ "$is_flattened" = true ]; then + if [ -z "$runtime" ]; then + pvFileName="sdk-productVersion.txt" + elif [[ "$runtime" == "dotnet" ]]; then + pvFileName="runtime-productVersion.txt" + else + pvFileName="$runtime-productVersion.txt" + fi + fi + + local download_link=null + + if [ -z "$package_download_link" ]; then + if [[ "$runtime" == "dotnet" ]]; then + download_link="$azure_feed/Runtime/$specific_version/${pvFileName}" + elif [[ "$runtime" == "aspnetcore" ]]; then + download_link="$azure_feed/aspnetcore/Runtime/$specific_version/${pvFileName}" + elif [ -z "$runtime" ]; then + download_link="$azure_feed/Sdk/$specific_version/${pvFileName}" + else + return 1 + fi + else + download_link="${package_download_link%/*}/${pvFileName}" + fi + + say_verbose "Constructed productVersion link: $download_link" + echo "$download_link" + return 0 +} + +# args: +# download link - $1 +# specific version - $2 +get_product_specific_version_from_download_link() +{ + eval $invocation + + local download_link="$1" + local specific_version="$2" + local specific_product_version="" + + if [ -z "$download_link" ]; then + echo "$specific_version" + return 0 + fi + + #get filename + filename="${download_link##*/}" + + #product specific version follows the product name + #for filename 'dotnet-sdk-3.1.404-linux-x64.tar.gz': the product version is 3.1.404 + IFS='-' + read -ra filename_elems <<< "$filename" + count=${#filename_elems[@]} + if [[ "$count" -gt 2 ]]; then + specific_product_version="${filename_elems[2]}" + else + specific_product_version=$specific_version + fi + unset IFS; + echo "$specific_product_version" + return 0 +} + +# args: +# azure_feed - $1 +# channel - $2 +# normalized_architecture - $3 +# specific_version - $4 +construct_legacy_download_link() { + eval $invocation + + local azure_feed="$1" + local channel="$2" + local normalized_architecture="$3" + local specific_version="${4//[$'\t\r\n']}" + + local distro_specific_osname + distro_specific_osname="$(get_legacy_os_name)" || return 1 + + local legacy_download_link=null + if [[ "$runtime" == "dotnet" ]]; then + legacy_download_link="$azure_feed/Runtime/$specific_version/dotnet-$distro_specific_osname-$normalized_architecture.$specific_version.tar.gz" + elif [ -z "$runtime" ]; then + legacy_download_link="$azure_feed/Sdk/$specific_version/dotnet-dev-$distro_specific_osname-$normalized_architecture.$specific_version.tar.gz" + else + return 1 + fi + + echo "$legacy_download_link" + return 0 +} + +get_user_install_path() { + eval $invocation + + if [ ! -z "${DOTNET_INSTALL_DIR:-}" ]; then + echo "$DOTNET_INSTALL_DIR" + else + echo "$HOME/.dotnet" + fi + return 0 +} + +# args: +# install_dir - $1 +resolve_installation_path() { + eval $invocation + + local install_dir=$1 + if [ "$install_dir" = "" ]; then + local user_install_path="$(get_user_install_path)" + say_verbose "resolve_installation_path: user_install_path=$user_install_path" + echo "$user_install_path" + return 0 + fi + + echo "$install_dir" + return 0 +} + +# args: +# relative_or_absolute_path - $1 +get_absolute_path() { + eval $invocation + + local relative_or_absolute_path=$1 + echo "$(cd "$(dirname "$1")" && pwd -P)/$(basename "$1")" + return 0 +} + +# args: +# override - $1 (boolean, true or false) +get_cp_options() { + eval $invocation + + local override="$1" + local override_switch="" + + if [ "$override" = false ]; then + override_switch="-n" + + # create temporary files to check if 'cp -u' is supported + tmp_dir="$(mktemp -d)" + tmp_file="$tmp_dir/testfile" + tmp_file2="$tmp_dir/testfile2" + + touch "$tmp_file" + + # use -u instead of -n if it's available + if cp -u "$tmp_file" "$tmp_file2" 2>/dev/null; then + override_switch="-u" + fi + + # clean up + rm -f "$tmp_file" "$tmp_file2" + rm -rf "$tmp_dir" + fi + + echo "$override_switch" +} + +# args: +# input_files - stdin +# root_path - $1 +# out_path - $2 +# override - $3 +copy_files_or_dirs_from_list() { + eval $invocation + + local root_path="$(remove_trailing_slash "$1")" + local out_path="$(remove_trailing_slash "$2")" + local override="$3" + local override_switch="$(get_cp_options "$override")" + + cat | uniq | while read -r file_path; do + local path="$(remove_beginning_slash "${file_path#$root_path}")" + local target="$out_path/$path" + if [ "$override" = true ] || (! ([ -d "$target" ] || [ -e "$target" ])); then + mkdir -p "$out_path/$(dirname "$path")" + if [ -d "$target" ]; then + rm -rf "$target" + fi + cp -R $override_switch "$root_path/$path" "$target" + fi + done +} + +# args: +# zip_uri - $1 +get_remote_file_size() { + local zip_uri="$1" + + if machine_has "curl"; then + file_size=$(curl -sI "$zip_uri" | grep -i content-length | awk '{ num = $2 + 0; print num }') + elif machine_has "wget"; then + file_size=$(wget --spider --server-response -O /dev/null "$zip_uri" 2>&1 | grep -i 'Content-Length:' | awk '{ num = $2 + 0; print num }') + else + say "Neither curl nor wget is available on this system." + return + fi + + if [ -n "$file_size" ]; then + say "Remote file $zip_uri size is $file_size bytes." + echo "$file_size" + else + say_verbose "Content-Length header was not extracted for $zip_uri." + echo "" + fi +} + +# args: +# zip_path - $1 +# out_path - $2 +# remote_file_size - $3 +extract_dotnet_package() { + eval $invocation + + local zip_path="$1" + local out_path="$2" + local remote_file_size="$3" + + local temp_out_path="$(mktemp -d "$temporary_file_template")" + + local failed=false + tar -xzf "$zip_path" -C "$temp_out_path" > /dev/null || failed=true + + local folders_with_version_regex='^.*/[0-9]+\.[0-9]+[^/]+/' + find "$temp_out_path" -type f | grep -Eo "$folders_with_version_regex" | sort | copy_files_or_dirs_from_list "$temp_out_path" "$out_path" false + find "$temp_out_path" -type f | grep -Ev "$folders_with_version_regex" | copy_files_or_dirs_from_list "$temp_out_path" "$out_path" "$override_non_versioned_files" + + validate_remote_local_file_sizes "$zip_path" "$remote_file_size" + + rm -rf "$temp_out_path" + if [ -z ${keep_zip+x} ]; then + rm -f "$zip_path" && say_verbose "Temporary archive file $zip_path was removed" + fi + + if [ "$failed" = true ]; then + say_err "Extraction failed" + return 1 + fi + return 0 +} + +# args: +# remote_path - $1 +# disable_feed_credential - $2 +get_http_header() +{ + eval $invocation + local remote_path="$1" + local disable_feed_credential="$2" + + local failed=false + local response + if machine_has "curl"; then + get_http_header_curl $remote_path $disable_feed_credential || failed=true + elif machine_has "wget"; then + get_http_header_wget $remote_path $disable_feed_credential || failed=true + else + failed=true + fi + if [ "$failed" = true ]; then + say_verbose "Failed to get HTTP header: '$remote_path'." + return 1 + fi + return 0 +} + +# args: +# remote_path - $1 +# disable_feed_credential - $2 +get_http_header_curl() { + eval $invocation + local remote_path="$1" + local disable_feed_credential="$2" + + remote_path_with_credential="$remote_path" + if [ "$disable_feed_credential" = false ]; then + remote_path_with_credential+="$feed_credential" + fi + + curl_options="-I -sSL --retry 5 --retry-delay 2 --connect-timeout 15 " + curl $curl_options "$remote_path_with_credential" 2>&1 || return 1 + return 0 +} + +# args: +# remote_path - $1 +# disable_feed_credential - $2 +get_http_header_wget() { + eval $invocation + local remote_path="$1" + local disable_feed_credential="$2" + local wget_options="-q -S --spider --tries 5 " + + local wget_options_extra='' + + # Test for options that aren't supported on all wget implementations. + if [[ $(wget -h 2>&1 | grep -E 'waitretry|connect-timeout') ]]; then + wget_options_extra="--waitretry 2 --connect-timeout 15 " + else + say "wget extra options are unavailable for this environment" + fi + + remote_path_with_credential="$remote_path" + if [ "$disable_feed_credential" = false ]; then + remote_path_with_credential+="$feed_credential" + fi + + wget $wget_options $wget_options_extra "$remote_path_with_credential" 2>&1 + + return $? +} + +# args: +# remote_path - $1 +# [out_path] - $2 - stdout if not provided +download() { + eval $invocation + + local remote_path="$1" + local out_path="${2:-}" + + if [[ "$remote_path" != "http"* ]]; then + cp "$remote_path" "$out_path" + return $? + fi + + local failed=false + local attempts=0 + while [ $attempts -lt 3 ]; do + attempts=$((attempts+1)) + failed=false + if machine_has "curl"; then + downloadcurl "$remote_path" "$out_path" || failed=true + elif machine_has "wget"; then + downloadwget "$remote_path" "$out_path" || failed=true + else + say_err "Missing dependency: neither curl nor wget was found." + exit 1 + fi + + if [ "$failed" = false ] || [ $attempts -ge 3 ] || { [ ! -z $http_code ] && [ $http_code = "404" ]; }; then + break + fi + + say "Download attempt #$attempts has failed: $http_code $download_error_msg" + say "Attempt #$((attempts+1)) will start in $((attempts*10)) seconds." + sleep $((attempts*10)) + done + + if [ "$failed" = true ]; then + say_verbose "Download failed: $remote_path" + return 1 + fi + return 0 +} + +# Updates global variables $http_code and $download_error_msg +downloadcurl() { + eval $invocation + unset http_code + unset download_error_msg + local remote_path="$1" + local out_path="${2:-}" + # Append feed_credential as late as possible before calling curl to avoid logging feed_credential + # Avoid passing URI with credentials to functions: note, most of them echoing parameters of invocation in verbose output. + local remote_path_with_credential="${remote_path}${feed_credential}" + local curl_options="--retry 20 --retry-delay 2 --connect-timeout 15 -sSL -f --create-dirs " + local curl_exit_code=0; + if [ -z "$out_path" ]; then + curl_output=$(curl $curl_options "$remote_path_with_credential" 2>&1) + curl_exit_code=$? + echo "$curl_output" + else + curl_output=$(curl $curl_options -o "$out_path" "$remote_path_with_credential" 2>&1) + curl_exit_code=$? + fi + + # Regression in curl causes curl with --retry to return a 0 exit code even when it fails to download a file - https://github.com/curl/curl/issues/17554 + if [ $curl_exit_code -eq 0 ] && echo "$curl_output" | grep -q "^curl: ([0-9]*) "; then + curl_exit_code=$(echo "$curl_output" | sed 's/curl: (\([0-9]*\)).*/\1/') + fi + + if [ $curl_exit_code -gt 0 ]; then + download_error_msg="Unable to download $remote_path." + # Check for curl timeout codes + if [[ $curl_exit_code == 7 || $curl_exit_code == 28 ]]; then + download_error_msg+=" Failed to reach the server: connection timeout." + else + local disable_feed_credential=false + local response=$(get_http_header_curl $remote_path $disable_feed_credential) + http_code=$( echo "$response" | awk '/^HTTP/{print $2}' | tail -1 ) + if [[ ! -z $http_code && $http_code != 2* ]]; then + download_error_msg+=" Returned HTTP status code: $http_code." + fi + fi + say_verbose "$download_error_msg" + return 1 + fi + return 0 +} + + +# Updates global variables $http_code and $download_error_msg +downloadwget() { + eval $invocation + unset http_code + unset download_error_msg + local remote_path="$1" + local out_path="${2:-}" + # Append feed_credential as late as possible before calling wget to avoid logging feed_credential + local remote_path_with_credential="${remote_path}${feed_credential}" + local wget_options="--tries 20 " + + local wget_options_extra='' + local wget_result='' + + # Test for options that aren't supported on all wget implementations. + if [[ $(wget -h 2>&1 | grep -E 'waitretry|connect-timeout') ]]; then + wget_options_extra="--waitretry 2 --connect-timeout 15 " + else + say "wget extra options are unavailable for this environment" + fi + + if [ -z "$out_path" ]; then + wget -q $wget_options $wget_options_extra -O - "$remote_path_with_credential" 2>&1 + wget_result=$? + else + wget $wget_options $wget_options_extra -O "$out_path" "$remote_path_with_credential" 2>&1 + wget_result=$? + fi + + if [[ $wget_result != 0 ]]; then + local disable_feed_credential=false + local response=$(get_http_header_wget $remote_path $disable_feed_credential) + http_code=$( echo "$response" | awk '/^ HTTP/{print $2}' | tail -1 ) + download_error_msg="Unable to download $remote_path." + if [[ ! -z $http_code && $http_code != 2* ]]; then + download_error_msg+=" Returned HTTP status code: $http_code." + # wget exit code 4 stands for network-issue + elif [[ $wget_result == 4 ]]; then + download_error_msg+=" Failed to reach the server: connection timeout." + fi + say_verbose "$download_error_msg" + return 1 + fi + + return 0 +} + +get_download_link_from_aka_ms() { + eval $invocation + + #quality is not supported for LTS or STS channel + #STS maps to current + if [[ ! -z "$normalized_quality" && ("$normalized_channel" == "LTS" || "$normalized_channel" == "STS") ]]; then + normalized_quality="" + say_warning "Specifying quality for STS or LTS channel is not supported, the quality will be ignored." + fi + + say_verbose "Retrieving primary payload URL from aka.ms for channel: '$normalized_channel', quality: '$normalized_quality', product: '$normalized_product', os: '$normalized_os', architecture: '$normalized_architecture'." + + #construct aka.ms link + aka_ms_link="https://aka.ms/dotnet" + if [ "$internal" = true ]; then + aka_ms_link="$aka_ms_link/internal" + fi + aka_ms_link="$aka_ms_link/$normalized_channel" + if [[ ! -z "$normalized_quality" ]]; then + aka_ms_link="$aka_ms_link/$normalized_quality" + fi + aka_ms_link="$aka_ms_link/$normalized_product-$normalized_os-$normalized_architecture.tar.gz" + say_verbose "Constructed aka.ms link: '$aka_ms_link'." + + #get HTTP response + #do not pass credentials as a part of the $aka_ms_link and do not apply credentials in the get_http_header function + #otherwise the redirect link would have credentials as well + #it would result in applying credentials twice to the resulting link and thus breaking it, and in echoing credentials to the output as a part of redirect link + disable_feed_credential=true + response="$(get_http_header $aka_ms_link $disable_feed_credential)" + + say_verbose "Received response: $response" + # Get results of all the redirects. + http_codes=$( echo "$response" | awk '$1 ~ /^HTTP/ {print $2}' ) + # They all need to be 301, otherwise some links are broken (except for the last, which is not a redirect but 200 or 404). + broken_redirects=$( echo "$http_codes" | sed '$d' | grep -v '301' ) + # The response may end without final code 2xx/4xx/5xx somehow, e.g. network restrictions on www.bing.com causes redirecting to bing.com fails with connection refused. + # In this case it should not exclude the last. + last_http_code=$( echo "$http_codes" | tail -n 1 ) + if ! [[ $last_http_code =~ ^(2|4|5)[0-9][0-9]$ ]]; then + broken_redirects=$( echo "$http_codes" | grep -v '301' ) + fi + + # All HTTP codes are 301 (Moved Permanently), the redirect link exists. + if [[ -z "$broken_redirects" ]]; then + aka_ms_download_link=$( echo "$response" | awk '$1 ~ /^Location/{print $2}' | tail -1 | tr -d '\r') + + if [[ -z "$aka_ms_download_link" ]]; then + say_verbose "The aka.ms link '$aka_ms_link' is not valid: failed to get redirect location." + return 1 + fi + + say_verbose "The redirect location retrieved: '$aka_ms_download_link'." + return 0 + else + say_verbose "The aka.ms link '$aka_ms_link' is not valid: received HTTP code: $(echo "$broken_redirects" | paste -sd "," -)." + return 1 + fi +} + +get_feeds_to_use() +{ + feeds=( + "https://builds.dotnet.microsoft.com/dotnet" + "https://ci.dot.net/public" + ) + + if [[ -n "$azure_feed" ]]; then + feeds=("$azure_feed") + fi + + if [[ -n "$uncached_feed" ]]; then + feeds=("$uncached_feed") + fi +} + +# THIS FUNCTION MAY EXIT (if the determined version is already installed). +generate_download_links() { + + download_links=() + specific_versions=() + effective_versions=() + link_types=() + + # If generate_akams_links returns false, no fallback to old links. Just terminate. + # This function may also 'exit' (if the determined version is already installed). + generate_akams_links || return + + # Check other feeds only if we haven't been able to find an aka.ms link. + if [[ "${#download_links[@]}" -lt 1 ]]; then + for feed in ${feeds[@]} + do + # generate_regular_links may also 'exit' (if the determined version is already installed). + generate_regular_links $feed || return + done + fi + + if [[ "${#download_links[@]}" -eq 0 ]]; then + say_err "Failed to resolve the exact version number." + return 1 + fi + + say_verbose "Generated ${#download_links[@]} links." + for link_index in ${!download_links[@]} + do + say_verbose "Link $link_index: ${link_types[$link_index]}, ${effective_versions[$link_index]}, ${download_links[$link_index]}" + done +} + +# THIS FUNCTION MAY EXIT (if the determined version is already installed). +generate_akams_links() { + local valid_aka_ms_link=true; + + normalized_version="$(to_lowercase "$version")" + if [[ "$normalized_version" != "latest" ]] && [ -n "$normalized_quality" ]; then + say_err "Quality and Version options are not allowed to be specified simultaneously. See https://learn.microsoft.com/dotnet/core/tools/dotnet-install-script#options for details." + return 1 + fi + + if [[ -n "$json_file" || "$normalized_version" != "latest" ]]; then + # aka.ms links are not needed when exact version is specified via command or json file + return + fi + + get_download_link_from_aka_ms || valid_aka_ms_link=false + + if [[ "$valid_aka_ms_link" == true ]]; then + say_verbose "Retrieved primary payload URL from aka.ms link: '$aka_ms_download_link'." + say_verbose "Downloading using legacy url will not be attempted." + + download_link=$aka_ms_download_link + + #get version from the path + IFS='/' + read -ra pathElems <<< "$download_link" + count=${#pathElems[@]} + specific_version="${pathElems[count-2]}" + unset IFS; + say_verbose "Version: '$specific_version'." + + #Retrieve effective version + effective_version="$(get_specific_product_version "$azure_feed" "$specific_version" "$download_link")" + + # Add link info to arrays + download_links+=($download_link) + specific_versions+=($specific_version) + effective_versions+=($effective_version) + link_types+=("aka.ms") + + # Check if the SDK version is already installed. + if [[ "$dry_run" != true ]] && is_dotnet_package_installed "$install_root" "$asset_relative_path" "$effective_version"; then + say "$asset_name with version '$effective_version' is already installed." + exit 0 + fi + + return 0 + fi + + # if quality is specified - exit with error - there is no fallback approach + if [ ! -z "$normalized_quality" ]; then + say_err "Failed to locate the latest version in the channel '$normalized_channel' with '$normalized_quality' quality for '$normalized_product', os: '$normalized_os', architecture: '$normalized_architecture'." + say_err "Refer to: https://aka.ms/dotnet-os-lifecycle for information on .NET Core support." + return 1 + fi + say_verbose "Falling back to latest.version file approach." +} + +# THIS FUNCTION MAY EXIT (if the determined version is already installed) +# args: +# feed - $1 +generate_regular_links() { + local feed="$1" + local valid_legacy_download_link=true + + specific_version=$(get_specific_version_from_version "$feed" "$channel" "$normalized_architecture" "$version" "$json_file") || specific_version='0' + + if [[ "$specific_version" == '0' ]]; then + say_verbose "Failed to resolve the specific version number using feed '$feed'" + return + fi + + effective_version="$(get_specific_product_version "$feed" "$specific_version")" + say_verbose "specific_version=$specific_version" + + download_link="$(construct_download_link "$feed" "$channel" "$normalized_architecture" "$specific_version" "$normalized_os")" + say_verbose "Constructed primary named payload URL: $download_link" + + # Add link info to arrays + download_links+=($download_link) + specific_versions+=($specific_version) + effective_versions+=($effective_version) + link_types+=("primary") + + legacy_download_link="$(construct_legacy_download_link "$feed" "$channel" "$normalized_architecture" "$specific_version")" || valid_legacy_download_link=false + + if [ "$valid_legacy_download_link" = true ]; then + say_verbose "Constructed legacy named payload URL: $legacy_download_link" + + download_links+=($legacy_download_link) + specific_versions+=($specific_version) + effective_versions+=($effective_version) + link_types+=("legacy") + else + legacy_download_link="" + say_verbose "Could not construct a legacy_download_link; omitting..." + fi + + # Check if the SDK version is already installed. + if [[ "$dry_run" != true ]] && is_dotnet_package_installed "$install_root" "$asset_relative_path" "$effective_version"; then + say "$asset_name with version '$effective_version' is already installed." + exit 0 + fi +} + +print_dry_run() { + + say "Payload URLs:" + + for link_index in "${!download_links[@]}" + do + say "URL #$link_index - ${link_types[$link_index]}: ${download_links[$link_index]}" + done + + resolved_version=${specific_versions[0]} + repeatable_command="./$script_name --version "\""$resolved_version"\"" --install-dir "\""$install_root"\"" --architecture "\""$normalized_architecture"\"" --os "\""$normalized_os"\""" + + if [ ! -z "$normalized_quality" ]; then + repeatable_command+=" --quality "\""$normalized_quality"\""" + fi + + if [[ "$runtime" == "dotnet" ]]; then + repeatable_command+=" --runtime "\""dotnet"\""" + elif [[ "$runtime" == "aspnetcore" ]]; then + repeatable_command+=" --runtime "\""aspnetcore"\""" + fi + + repeatable_command+="$non_dynamic_parameters" + + if [ -n "$feed_credential" ]; then + repeatable_command+=" --feed-credential "\"""\""" + fi + + say "Repeatable invocation: $repeatable_command" +} + +calculate_vars() { + eval $invocation + + script_name=$(basename "$0") + normalized_architecture="$(get_normalized_architecture_from_architecture "$architecture")" + say_verbose "Normalized architecture: '$normalized_architecture'." + normalized_os="$(get_normalized_os "$user_defined_os")" + say_verbose "Normalized OS: '$normalized_os'." + normalized_quality="$(get_normalized_quality "$quality")" + say_verbose "Normalized quality: '$normalized_quality'." + normalized_channel="$(get_normalized_channel "$channel")" + say_verbose "Normalized channel: '$normalized_channel'." + normalized_product="$(get_normalized_product "$runtime")" + say_verbose "Normalized product: '$normalized_product'." + install_root="$(resolve_installation_path "$install_dir")" + say_verbose "InstallRoot: '$install_root'." + + normalized_architecture="$(get_normalized_architecture_for_specific_sdk_version "$version" "$normalized_channel" "$normalized_architecture")" + + if [[ "$runtime" == "dotnet" ]]; then + asset_relative_path="shared/Microsoft.NETCore.App" + asset_name=".NET Core Runtime" + elif [[ "$runtime" == "aspnetcore" ]]; then + asset_relative_path="shared/Microsoft.AspNetCore.App" + asset_name="ASP.NET Core Runtime" + elif [ -z "$runtime" ]; then + asset_relative_path="sdk" + asset_name=".NET Core SDK" + fi + + get_feeds_to_use +} + +install_dotnet() { + eval $invocation + local download_failed=false + local download_completed=false + local remote_file_size=0 + + mkdir -p "$install_root" + zip_path="${zip_path:-$(mktemp "$temporary_file_template")}" + say_verbose "Archive path: $zip_path" + + for link_index in "${!download_links[@]}" + do + download_link="${download_links[$link_index]}" + specific_version="${specific_versions[$link_index]}" + effective_version="${effective_versions[$link_index]}" + link_type="${link_types[$link_index]}" + + say "Attempting to download using $link_type link $download_link" + + # The download function will set variables $http_code and $download_error_msg in case of failure. + download_failed=false + download "$download_link" "$zip_path" 2>&1 || download_failed=true + + if [ "$download_failed" = true ]; then + case $http_code in + 404) + say "The resource at $link_type link '$download_link' is not available." + ;; + *) + say "Failed to download $link_type link '$download_link': $http_code $download_error_msg" + ;; + esac + rm -f "$zip_path" 2>&1 && say_verbose "Temporary archive file $zip_path was removed" + else + download_completed=true + break + fi + done + + if [[ "$download_completed" == false ]]; then + say_err "Could not find \`$asset_name\` with version = $specific_version" + say_err "Refer to: https://aka.ms/dotnet-os-lifecycle for information on .NET Core support" + return 1 + fi + + remote_file_size="$(get_remote_file_size "$download_link")" + + say "Extracting archive from $download_link" + extract_dotnet_package "$zip_path" "$install_root" "$remote_file_size" || return 1 + + # Check if the SDK version is installed; if not, fail the installation. + # if the version contains "RTM" or "servicing"; check if a 'release-type' SDK version is installed. + if [[ $specific_version == *"rtm"* || $specific_version == *"servicing"* ]]; then + IFS='-' + read -ra verArr <<< "$specific_version" + release_version="${verArr[0]}" + unset IFS; + say_verbose "Checking installation: version = $release_version" + if is_dotnet_package_installed "$install_root" "$asset_relative_path" "$release_version"; then + say "Installed version is $effective_version" + return 0 + fi + fi + + # Check if the standard SDK version is installed. + say_verbose "Checking installation: version = $effective_version" + if is_dotnet_package_installed "$install_root" "$asset_relative_path" "$effective_version"; then + say "Installed version is $effective_version" + return 0 + fi + + # Version verification failed. More likely something is wrong either with the downloaded content or with the verification algorithm. + say_err "Failed to verify the version of installed \`$asset_name\`.\nInstallation source: $download_link.\nInstallation location: $install_root.\nReport the bug at https://github.com/dotnet/install-scripts/issues." + say_err "\`$asset_name\` with version = $effective_version failed to install with an error." + return 1 +} + +args=("$@") + +local_version_file_relative_path="/.version" +bin_folder_relative_path="" +temporary_file_template="${TMPDIR:-/tmp}/dotnet.XXXXXXXXX" + +channel="LTS" +version="Latest" +json_file="" +install_dir="" +architecture="" +dry_run=false +no_path=false +azure_feed="" +uncached_feed="" +feed_credential="" +verbose=false +runtime="" +runtime_id="" +quality="" +internal=false +override_non_versioned_files=true +non_dynamic_parameters="" +user_defined_os="" + +while [ $# -ne 0 ] +do + name="$1" + case "$name" in + -c|--channel|-[Cc]hannel) + shift + channel="$1" + ;; + -v|--version|-[Vv]ersion) + shift + version="$1" + ;; + -q|--quality|-[Qq]uality) + shift + quality="$1" + ;; + --internal|-[Ii]nternal) + internal=true + non_dynamic_parameters+=" $name" + ;; + -i|--install-dir|-[Ii]nstall[Dd]ir) + shift + install_dir="$1" + ;; + --arch|--architecture|-[Aa]rch|-[Aa]rchitecture) + shift + architecture="$1" + ;; + --os|-[Oo][SS]) + shift + user_defined_os="$1" + ;; + --shared-runtime|-[Ss]hared[Rr]untime) + say_warning "The --shared-runtime flag is obsolete and may be removed in a future version of this script. The recommended usage is to specify '--runtime dotnet'." + if [ -z "$runtime" ]; then + runtime="dotnet" + fi + ;; + --runtime|-[Rr]untime) + shift + runtime="$1" + if [[ "$runtime" != "dotnet" ]] && [[ "$runtime" != "aspnetcore" ]]; then + say_err "Unsupported value for --runtime: '$1'. Valid values are 'dotnet' and 'aspnetcore'." + if [[ "$runtime" == "windowsdesktop" ]]; then + say_err "WindowsDesktop archives are manufactured for Windows platforms only." + fi + exit 1 + fi + ;; + --dry-run|-[Dd]ry[Rr]un) + dry_run=true + ;; + --no-path|-[Nn]o[Pp]ath) + no_path=true + non_dynamic_parameters+=" $name" + ;; + --verbose|-[Vv]erbose) + verbose=true + non_dynamic_parameters+=" $name" + ;; + --azure-feed|-[Aa]zure[Ff]eed) + shift + azure_feed="$1" + non_dynamic_parameters+=" $name "\""$1"\""" + ;; + --uncached-feed|-[Uu]ncached[Ff]eed) + shift + uncached_feed="$1" + non_dynamic_parameters+=" $name "\""$1"\""" + ;; + --feed-credential|-[Ff]eed[Cc]redential) + shift + feed_credential="$1" + #feed_credential should start with "?", for it to be added to the end of the link. + #adding "?" at the beginning of the feed_credential if needed. + [[ -z "$(echo $feed_credential)" ]] || [[ $feed_credential == \?* ]] || feed_credential="?$feed_credential" + ;; + --runtime-id|-[Rr]untime[Ii]d) + shift + runtime_id="$1" + non_dynamic_parameters+=" $name "\""$1"\""" + say_warning "Use of --runtime-id is obsolete and should be limited to the versions below 2.1. To override architecture, use --architecture option instead. To override OS, use --os option instead." + ;; + --jsonfile|-[Jj][Ss]on[Ff]ile) + shift + json_file="$1" + ;; + --skip-non-versioned-files|-[Ss]kip[Nn]on[Vv]ersioned[Ff]iles) + override_non_versioned_files=false + non_dynamic_parameters+=" $name" + ;; + --keep-zip|-[Kk]eep[Zz]ip) + keep_zip=true + non_dynamic_parameters+=" $name" + ;; + --zip-path|-[Zz]ip[Pp]ath) + shift + zip_path="$1" + ;; + -?|--?|-h|--help|-[Hh]elp) + script_name="dotnet-install.sh" + echo ".NET Tools Installer" + echo "Usage:" + echo " # Install a .NET SDK of a given Quality from a given Channel" + echo " $script_name [-c|--channel ] [-q|--quality ]" + echo " # Install a .NET SDK of a specific public version" + echo " $script_name [-v|--version ]" + echo " $script_name -h|-?|--help" + echo "" + echo "$script_name is a simple command line interface for obtaining dotnet cli." + echo " Note that the intended use of this script is for Continuous Integration (CI) scenarios, where:" + echo " - The SDK needs to be installed without user interaction and without admin rights." + echo " - The SDK installation doesn't need to persist across multiple CI runs." + echo " To set up a development environment or to run apps, use installers rather than this script. Visit https://dotnet.microsoft.com/download to get the installer." + echo "" + echo "Options:" + echo " -c,--channel Download from the channel specified, Defaults to \`$channel\`." + echo " -Channel" + echo " Possible values:" + echo " - STS - the most recent Standard Term Support release" + echo " - LTS - the most recent Long Term Support release" + echo " - 2-part version in a format A.B - represents a specific release" + echo " examples: 2.0; 1.0" + echo " - 3-part version in a format A.B.Cxx - represents a specific SDK release" + echo " examples: 5.0.1xx, 5.0.2xx." + echo " Supported since 5.0 release" + echo " Warning: Value 'Current' is deprecated for the Channel parameter. Use 'STS' instead." + echo " Note: The version parameter overrides the channel parameter when any version other than 'latest' is used." + echo " -v,--version Use specific VERSION, Defaults to \`$version\`." + echo " -Version" + echo " Possible values:" + echo " - latest - the latest build on specific channel" + echo " - 3-part version in a format A.B.C - represents specific version of build" + echo " examples: 2.0.0-preview2-006120; 1.1.0" + echo " -q,--quality Download the latest build of specified quality in the channel." + echo " -Quality" + echo " The possible values are: daily, preview, GA." + echo " Works only in combination with channel. Not applicable for STS and LTS channels and will be ignored if those channels are used." + echo " For SDK use channel in A.B.Cxx format. Using quality for SDK together with channel in A.B format is not supported." + echo " Supported since 5.0 release." + echo " Note: The version parameter overrides the channel parameter when any version other than 'latest' is used, and therefore overrides the quality." + echo " --internal,-Internal Download internal builds. Requires providing credentials via --feed-credential parameter." + echo " --feed-credential Token to access Azure feed. Used as a query string to append to the Azure feed." + echo " -FeedCredential This parameter typically is not specified." + echo " -i,--install-dir Install under specified location (see Install Location below)" + echo " -InstallDir" + echo " --architecture Architecture of dotnet binaries to be installed, Defaults to \`$architecture\`." + echo " --arch,-Architecture,-Arch" + echo " Possible values: x64, arm, arm64, s390x, ppc64le and loongarch64" + echo " --os Specifies operating system to be used when selecting the installer." + echo " Overrides the OS determination approach used by the script. Supported values: osx, linux, linux-musl, freebsd, rhel.6." + echo " In case any other value is provided, the platform will be determined by the script based on machine configuration." + echo " Not supported for legacy links. Use --runtime-id to specify platform for legacy links." + echo " Refer to: https://aka.ms/dotnet-os-lifecycle for more information." + echo " --runtime Installs a shared runtime only, without the SDK." + echo " -Runtime" + echo " Possible values:" + echo " - dotnet - the Microsoft.NETCore.App shared runtime" + echo " - aspnetcore - the Microsoft.AspNetCore.App shared runtime" + echo " --dry-run,-DryRun Do not perform installation. Display download link." + echo " --no-path, -NoPath Do not set PATH for the current process." + echo " --verbose,-Verbose Display diagnostics information." + echo " --azure-feed,-AzureFeed For internal use only." + echo " Allows using a different storage to download SDK archives from." + echo " --uncached-feed,-UncachedFeed For internal use only." + echo " Allows using a different storage to download SDK archives from." + echo " --skip-non-versioned-files Skips non-versioned files if they already exist, such as the dotnet executable." + echo " -SkipNonVersionedFiles" + echo " --jsonfile Determines the SDK version from a user specified global.json file." + echo " Note: global.json must have a value for 'SDK:Version'" + echo " --keep-zip,-KeepZip If set, downloaded file is kept." + echo " --zip-path, -ZipPath If set, downloaded file is stored at the specified path." + echo " -?,--?,-h,--help,-Help Shows this help message" + echo "" + echo "Install Location:" + echo " Location is chosen in following order:" + echo " - --install-dir option" + echo " - Environmental variable DOTNET_INSTALL_DIR" + echo " - $HOME/.dotnet" + exit 0 + ;; + *) + say_err "Unknown argument \`$name\`" + exit 1 + ;; + esac + + shift +done + +say_verbose "Note that the intended use of this script is for Continuous Integration (CI) scenarios, where:" +say_verbose "- The SDK needs to be installed without user interaction and without admin rights." +say_verbose "- The SDK installation doesn't need to persist across multiple CI runs." +say_verbose "To set up a development environment or to run apps, use installers rather than this script. Visit https://dotnet.microsoft.com/download to get the installer.\n" + +if [ "$internal" = true ] && [ -z "$(echo $feed_credential)" ]; then + message="Provide credentials via --feed-credential parameter." + if [ "$dry_run" = true ]; then + say_warning "$message" + else + say_err "$message" + exit 1 + fi +fi + +check_min_reqs +calculate_vars +# generate_regular_links call below will 'exit' if the determined version is already installed. +generate_download_links + +if [[ "$dry_run" = true ]]; then + print_dry_run + exit 0 +fi + +install_dotnet + +bin_path="$(get_absolute_path "$(combine_paths "$install_root" "$bin_folder_relative_path")")" +if [ "$no_path" = false ]; then + say "Adding to current process PATH: \`$bin_path\`. Note: This change will be visible only when sourcing script." + export PATH="$bin_path":"$PATH" +else + say "Binaries of dotnet can be found in $bin_path" +fi + +say "Note that the script does not resolve dependencies during installation." +say "To check the list of dependencies, go to https://learn.microsoft.com/dotnet/core/install, select your operating system and check the \"Dependencies\" section." +say "Installation finished successfully." From b7b0d5f3a28f7f3dd5a557e88e2b54bb7b846f87 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 22 Aug 2025 20:38:12 +0000 Subject: [PATCH 03/10] Add comprehensive .github/copilot-instructions.md with validated commands and scenarios Co-authored-by: KSemenenko <4385716+KSemenenko@users.noreply.github.com> --- .github/copilot-instructions.md | 225 + .../ManagedCode.Communication.Tests.trx | 4260 +---------------- dotnet-install.sh.1 | 1888 ++++++++ 3 files changed, 2302 insertions(+), 4071 deletions(-) create mode 100644 .github/copilot-instructions.md create mode 100644 dotnet-install.sh.1 diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000..bcfe580 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,225 @@ +# ManagedCode.Communication + +A high-performance .NET 9.0 library providing Result types and railway-oriented programming patterns for robust error handling in distributed applications. + +**ALWAYS follow these instructions first and only fall back to search or bash commands when you encounter unexpected information that does not match the instructions below.** + +## Working Effectively + +### Bootstrap and Build the Repository + +**NEVER CANCEL builds or tests** - they complete quickly but timeouts should be generous. + +1. **Install .NET 9.0 SDK (REQUIRED):** + ```bash + wget -q https://dot.net/v1/dotnet-install.sh && chmod +x dotnet-install.sh + ./dotnet-install.sh --version 9.0.100 --install-dir ~/.dotnet + export PATH="/home/runner/.dotnet:$PATH" + ``` + - Verification: `dotnet --version` should show `9.0.100` + - **CRITICAL**: The project targets .NET 9.0 and will NOT build with older SDKs + +2. **Restore packages (takes ~7 seconds):** + ```bash + dotnet restore ManagedCode.Communication/ManagedCode.Communication.csproj + dotnet restore ManagedCode.Communication.AspNetCore/ManagedCode.Communication.AspNetCore.csproj + dotnet restore ManagedCode.Communication.Orleans/ManagedCode.Communication.Orleans.csproj + dotnet restore ManagedCode.Communication.Tests/ManagedCode.Communication.Tests.csproj + dotnet restore ManagedCode.Communication.Benchmark/ManagedCode.Communication.Benchmark.csproj + ``` + - **NEVER CANCEL**: Set timeout to 300+ seconds. Build may take up to 5 minutes on slow connections. + +3. **Build all projects (takes ~12 seconds):** + ```bash + dotnet build ManagedCode.Communication/ManagedCode.Communication.csproj --configuration Release + dotnet build ManagedCode.Communication.AspNetCore/ManagedCode.Communication.AspNetCore.csproj --configuration Release + dotnet build ManagedCode.Communication.Orleans/ManagedCode.Communication.Orleans.csproj --configuration Release + dotnet build ManagedCode.Communication.Tests/ManagedCode.Communication.Tests.csproj --configuration Release + dotnet build ManagedCode.Communication.Benchmark/ManagedCode.Communication.Benchmark.csproj --configuration Release + ``` + - **NEVER CANCEL**: Set timeout to 600+ seconds. Full build may take up to 10 minutes. + +### Testing + +**Run all tests (takes ~5 seconds, 638 tests):** +```bash +dotnet test ManagedCode.Communication.Tests/ManagedCode.Communication.Tests.csproj --configuration Release --no-build --verbosity normal +``` +- **NEVER CANCEL**: Set timeout to 300+ seconds. Tests may take up to 5 minutes in CI environments. +- **Expected result**: All 638 tests pass with 70%+ code coverage +- Tests include ASP.NET Core integration tests with real HTTP requests and SignalR hubs + +### Code Quality and Formatting + +**Format code (IMPORTANT - there are known line ending issues):** +```bash +# Format individual projects (the repository has CRLF line ending issues on Linux) +dotnet format ManagedCode.Communication/ManagedCode.Communication.csproj --verbosity minimal +dotnet format ManagedCode.Communication.AspNetCore/ManagedCode.Communication.AspNetCore.csproj --verbosity minimal +dotnet format ManagedCode.Communication.Orleans/ManagedCode.Communication.Orleans.csproj --verbosity minimal +dotnet format ManagedCode.Communication.Tests/ManagedCode.Communication.Tests.csproj --verbosity minimal +``` +- **NOTE**: The repository uses CRLF line endings (.editorconfig sets `end_of_line = crlf`) which may cause formatting errors on Linux. This is expected behavior. +- **DO NOT** try to fix line ending issues - they are intentional for Windows compatibility + +## Validation Scenarios + +**ALWAYS test actual functionality after making changes** by running through these complete scenarios: + +### 1. Basic Library Functionality Test +Create a test console app to verify core Result functionality: + +```bash +cd /tmp +dotnet new console -n TestLibrary +cd TestLibrary +dotnet add reference /home/runner/work/Communication/Communication/ManagedCode.Communication/ManagedCode.Communication.csproj +``` + +Test Program.cs content: +```csharp +using ManagedCode.Communication; +using ManagedCode.Communication.Extensions; +using System; + +Console.WriteLine("Testing ManagedCode.Communication Library"); + +// Test basic Result creation +var successResult = Result.Succeed(); +var failureResult = Result.Fail("Something went wrong"); +Console.WriteLine($"Success: {successResult.IsSuccess}, Failure: {failureResult.IsSuccess}"); + +// Test Result with values +var userResult = Result.Succeed("John Doe"); +var notFoundResult = Result.FailNotFound("User not found"); +Console.WriteLine($"User: {userResult.Value}, NotFound: {notFoundResult.Problem?.Title}"); + +// Test railway-oriented programming chain +var chainResult = ValidateEmail("test@example.com") + .Then(email => Result.Succeed(email.ToLower())) + .Then(email => Result.Succeed($"Processed: {email}")); +Console.WriteLine($"Chain result: {chainResult.IsSuccess}, Value: {chainResult.Value}"); + +static Result ValidateEmail(string email) => + email.Contains("@") ? Result.Succeed(email) : Result.FailValidation(("email", "Invalid format")); +``` + +Run: `dotnet run` +**Expected output**: Success messages showing Result types work correctly with railway-oriented programming. + +### 2. ASP.NET Core Integration Test +The test suite includes real ASP.NET Core integration tests that: +- Start an actual web server on localhost +- Test Result to HTTP status code mapping (200, 400, 403, 404, 500) +- Test SignalR hub integration with Result types +- Test authentication and authorization flows + +Run the integration tests to verify web functionality: +```bash +dotnet test ManagedCode.Communication.Tests/ManagedCode.Communication.Tests.csproj --filter "AspNetCore" --verbosity normal +``` + +### 3. Performance Benchmark Test +Test the benchmark suite (interactive): +```bash +dotnet run -c Release --project ManagedCode.Communication.Benchmark/ManagedCode.Communication.Benchmark.csproj -- --job dry +# When prompted, enter "*" to run all benchmarks or "0" for specific benchmark +``` +**Expected**: Benchmark starts and shows performance comparisons between Result types and alternatives. + +## Key Projects in the Codebase + +### Core Projects +- **ManagedCode.Communication** - Core Result types, railway-oriented programming extensions, command pattern +- **ManagedCode.Communication.AspNetCore** - ASP.NET Core filters, middleware, and Result-to-HTTP mapping +- **ManagedCode.Communication.Orleans** - Microsoft Orleans serialization support for distributed Result types +- **ManagedCode.Communication.Tests** - Comprehensive test suite (638 tests) +- **ManagedCode.Communication.Benchmark** - BenchmarkDotNet performance testing + +### Key Source Locations +- **Result Types**: `ManagedCode.Communication/Result/`, `ManagedCode.Communication/ResultT/` +- **Railway Extensions**: `ManagedCode.Communication/Extensions/RailwayExtensions*.cs` +- **Command Pattern**: `ManagedCode.Communication/Commands/` +- **ASP.NET Core Filters**: `ManagedCode.Communication.AspNetCore/Filters/` +- **Orleans Serializers**: `ManagedCode.Communication.Orleans/Serializers/` + +## Build Timing Expectations + +**All times measured on standard GitHub Actions runners:** + +| Operation | Expected Time | Timeout Setting | +|-----------|---------------|-----------------| +| .NET 9 SDK Install | 30-60 seconds | 300 seconds | +| Package Restore | 5-10 seconds | 300 seconds | +| Full Build | 10-15 seconds | 600 seconds | +| Test Execution | 4-6 seconds | 300 seconds | +| Format Check | 3-8 seconds | 300 seconds | +| Single Project Build | 1-4 seconds | 300 seconds | + +**CRITICAL: NEVER CANCEL** any of these operations. Always wait for completion. + +## Common Development Tasks + +### Adding New Result Types +- Extend base classes in `ManagedCode.Communication/Result/` or `ManagedCode.Communication/ResultT/` +- Add corresponding tests in `ManagedCode.Communication.Tests/` +- Update ASP.NET Core mappings in `ManagedCode.Communication.AspNetCore/Extensions/ResultExtensions.cs` + +### Adding ASP.NET Core Features +- Create filters in `ManagedCode.Communication.AspNetCore/Filters/` +- Add extension methods in `ManagedCode.Communication.AspNetCore/Extensions/` +- Test with real HTTP scenarios in test project's TestApp + +### Performance Testing +- Add benchmarks to `ManagedCode.Communication.Benchmark/` +- Use BenchmarkDotNet attributes for proper measurement +- Always compare against existing baseline implementations + +## Troubleshooting + +### Build Issues +- **".NET 9.0 not found"**: Install .NET 9.0 SDK using the exact commands above +- **"Project not found"**: Use individual project files, not solution file (`.slnx` format not fully supported) +- **"Format errors"**: Line ending issues are expected on Linux due to CRLF settings + +### Test Issues +- **Tests timeout**: ASP.NET Core integration tests start real servers and may take longer in CI environments +- **Orleans tests fail**: Ensure all projects are built before running tests (Orleans needs compiled assemblies) + +### Expected CI Workflow +The `.github/workflows/ci.yml` runs: +1. .NET 9.0 setup +2. `dotnet restore` +3. `dotnet build --configuration Release --no-restore` +4. `dotnet test --configuration Release --no-build` +5. Code coverage upload + +**Always run the same sequence locally** to ensure CI compatibility. + +## Library Usage Examples + +The library provides Result types for error handling without exceptions: + +```csharp +// Basic usage +Result user = await GetUserAsync(id); +if (user.IsSuccess) + Console.WriteLine($"Found: {user.Value.Name}"); +else + Console.WriteLine($"Error: {user.Problem.Title}"); + +// Railway-oriented programming +var result = await ValidateUserAsync(userData) + .ThenAsync(user => SaveUserAsync(user)) + .ThenAsync(user => SendWelcomeEmailAsync(user)) + .ThenAsync(user => LogUserCreationAsync(user)); + +// ASP.NET Core integration (automatic HTTP status mapping) +[HttpPost] +public Result CreateUser(CreateUserRequest request) => + ValidateRequest(request) + .Then(CreateUserFromRequest) + .Then(SaveUserToDatabase); +``` + +Always test these patterns when making changes to ensure the library's core value proposition remains intact. \ No newline at end of file diff --git a/ManagedCode.Communication.Tests/ManagedCode.Communication.Tests.trx b/ManagedCode.Communication.Tests/ManagedCode.Communication.Tests.trx index 31caa58..443530b 100644 --- a/ManagedCode.Communication.Tests/ManagedCode.Communication.Tests.trx +++ b/ManagedCode.Communication.Tests/ManagedCode.Communication.Tests.trx @@ -1,3855 +1,218 @@  - - - - + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + [xUnit.net 00:00:00.00] xUnit.net VSTest Adapter v3.1.4+50e68bbb8b (64-bit .NET 9.0.0) [xUnit.net 00:00:00.10] Discovering: ManagedCode.Communication.Tests -[xUnit.net 00:00:00.21] Discovered: ManagedCode.Communication.Tests -[xUnit.net 00:00:00.28] Starting: ManagedCode.Communication.Tests -Value: 20 +[xUnit.net 00:00:00.20] Discovered: ManagedCode.Communication.Tests +[xUnit.net 00:00:00.23] Starting: ManagedCode.Communication.Tests info: Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager[62] User profile is available. Using '/home/runner/.aspnet/DataProtection-Keys' as key repository; keys will not be encrypted at rest. info: Microsoft.AspNetCore.Hosting.Diagnostics[1] @@ -3861,11 +224,11 @@ info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[102] info: Microsoft.AspNetCore.Mvc.Infrastructure.ObjectResultExecutor[1] Executing ObjectResult, writing value of type 'ManagedCode.Communication.CollectionResultT.CollectionResult`1[[ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestModel, ManagedCode.Communication.Tests, Version=9.6.2.0, Culture=neutral, PublicKeyToken=null]]'. info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[105] - Executed action ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.GetCollectionSuccess (ManagedCode.Communication.Tests) in 110.497ms + Executed action ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.GetCollectionSuccess (ManagedCode.Communication.Tests) in 55.0109ms info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1] Executed endpoint 'ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.GetCollectionSuccess (ManagedCode.Communication.Tests)' info: Microsoft.AspNetCore.Hosting.Diagnostics[2] - Request finished HTTP/1.1 GET http://localhost/test/collection-success - 200 - application/json;+charset=utf-8 206.6610ms + Request finished HTTP/1.1 GET http://localhost/test/collection-success - 200 - application/json;+charset=utf-8 98.4968ms info: Microsoft.AspNetCore.Hosting.Diagnostics[1] Request starting HTTP/1.1 POST http://localhost/test/validate - application/json;+charset=utf-8 38 info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0] @@ -3877,11 +240,11 @@ warn: ManagedCode.Communication.AspNetCore.Filters.CommunicationModelValidationF info: Microsoft.AspNetCore.Mvc.Infrastructure.ObjectResultExecutor[1] Executing BadRequestObjectResult, writing value of type 'ManagedCode.Communication.Result'. info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[105] - Executed action ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.Validate (ManagedCode.Communication.Tests) in 38.1819ms + Executed action ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.Validate (ManagedCode.Communication.Tests) in 46.9148ms info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1] Executed endpoint 'ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.Validate (ManagedCode.Communication.Tests)' info: Microsoft.AspNetCore.Hosting.Diagnostics[2] - Request finished HTTP/1.1 POST http://localhost/test/validate - 400 - application/json;+charset=utf-8 51.8960ms + Request finished HTTP/1.1 POST http://localhost/test/validate - 400 - application/json;+charset=utf-8 56.9092ms info: Microsoft.AspNetCore.Hosting.Diagnostics[1] Request starting HTTP/1.1 GET http://localhost/test/custom-problem - - - info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0] @@ -3891,11 +254,11 @@ info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[102] info: Microsoft.AspNetCore.Mvc.Infrastructure.ObjectResultExecutor[1] Executing ObjectResult, writing value of type 'ManagedCode.Communication.Problem'. info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[105] - Executed action ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.CustomProblem (ManagedCode.Communication.Tests) in 9.9844ms + Executed action ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.CustomProblem (ManagedCode.Communication.Tests) in 2.2734ms info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1] Executed endpoint 'ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.CustomProblem (ManagedCode.Communication.Tests)' info: Microsoft.AspNetCore.Hosting.Diagnostics[2] - Request finished HTTP/1.1 GET http://localhost/test/custom-problem - 409 - application/json;+charset=utf-8 16.0905ms + Request finished HTTP/1.1 GET http://localhost/test/custom-problem - 409 - application/json;+charset=utf-8 4.3435ms info: Microsoft.AspNetCore.Hosting.Diagnostics[1] Request starting HTTP/1.1 GET http://localhost/test/throw-exception - - - info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0] @@ -3906,7 +269,7 @@ fail: ManagedCode.Communication.AspNetCore.Filters.CommunicationExceptionFilter[ Unhandled exception in Test.ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.ThrowException (ManagedCode.Communication.Tests) System.InvalidOperationException: This is a test exception for integration testing at ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.ThrowException() in /_/ManagedCode.Communication.Tests/Common/TestApp/Controllers/TestController.cs:line 130 - at lambda_method104(Closure, Object, Object[]) + at lambda_method81(Closure, Object, Object[]) at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.SyncObjectResultExecutor.Execute(ActionContext actionContext, IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeActionMethodAsync() at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted) @@ -3922,11 +285,11 @@ info: ManagedCode.Communication.AspNetCore.Filters.CommunicationExceptionFilter[ info: Microsoft.AspNetCore.Mvc.Infrastructure.ObjectResultExecutor[1] Executing ObjectResult, writing value of type 'ManagedCode.Communication.Result'. info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[105] - Executed action ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.ThrowException (ManagedCode.Communication.Tests) in 17.1595ms + Executed action ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.ThrowException (ManagedCode.Communication.Tests) in 8.6866ms info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1] Executed endpoint 'ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.ThrowException (ManagedCode.Communication.Tests)' info: Microsoft.AspNetCore.Hosting.Diagnostics[2] - Request finished HTTP/1.1 GET http://localhost/test/throw-exception - 400 - application/json;+charset=utf-8 22.0381ms + Request finished HTTP/1.1 GET http://localhost/test/throw-exception - 400 - application/json;+charset=utf-8 9.4554ms info: Microsoft.AspNetCore.Hosting.Diagnostics[1] Request starting HTTP/1.1 GET http://localhost/test/collection-empty - - - info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0] @@ -3936,11 +299,11 @@ info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[102] info: Microsoft.AspNetCore.Mvc.Infrastructure.ObjectResultExecutor[1] Executing ObjectResult, writing value of type 'ManagedCode.Communication.CollectionResultT.CollectionResult`1[[ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestModel, ManagedCode.Communication.Tests, Version=9.6.2.0, Culture=neutral, PublicKeyToken=null]]'. info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[105] - Executed action ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.GetCollectionEmpty (ManagedCode.Communication.Tests) in 0.485ms + Executed action ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.GetCollectionEmpty (ManagedCode.Communication.Tests) in 0.6124ms info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1] Executed endpoint 'ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.GetCollectionEmpty (ManagedCode.Communication.Tests)' info: Microsoft.AspNetCore.Hosting.Diagnostics[2] - Request finished HTTP/1.1 GET http://localhost/test/collection-empty - 200 - application/json;+charset=utf-8 2.1438ms + Request finished HTTP/1.1 GET http://localhost/test/collection-empty - 200 - application/json;+charset=utf-8 2.9464ms info: Microsoft.AspNetCore.Hosting.Diagnostics[1] Request starting HTTP/1.1 POST http://localhost/test/validate - application/json;+charset=utf-8 55 info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0] @@ -3950,11 +313,11 @@ info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[102] info: Microsoft.AspNetCore.Mvc.Infrastructure.ObjectResultExecutor[1] Executing OkObjectResult, writing value of type 'System.String'. info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[105] - Executed action ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.Validate (ManagedCode.Communication.Tests) in 2.3696ms + Executed action ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.Validate (ManagedCode.Communication.Tests) in 2.0563ms info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1] Executed endpoint 'ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.Validate (ManagedCode.Communication.Tests)' info: Microsoft.AspNetCore.Hosting.Diagnostics[2] - Request finished HTTP/1.1 POST http://localhost/test/validate - 200 - text/plain;+charset=utf-8 3.0451ms + Request finished HTTP/1.1 POST http://localhost/test/validate - 200 - text/plain;+charset=utf-8 2.3331ms info: Microsoft.AspNetCore.Hosting.Diagnostics[1] Request starting HTTP/1.1 GET http://localhost/test/result-notfound - - - info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0] @@ -3964,11 +327,11 @@ info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[102] info: Microsoft.AspNetCore.Mvc.Infrastructure.ObjectResultExecutor[1] Executing ObjectResult, writing value of type 'ManagedCode.Communication.Result`1[[System.String, System.Private.CoreLib, Version=9.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]]'. info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[105] - Executed action ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.GetResultNotFoundTest (ManagedCode.Communication.Tests) in 9.8268ms + Executed action ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.GetResultNotFoundTest (ManagedCode.Communication.Tests) in 6.5134ms info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1] Executed endpoint 'ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.GetResultNotFoundTest (ManagedCode.Communication.Tests)' info: Microsoft.AspNetCore.Hosting.Diagnostics[2] - Request finished HTTP/1.1 GET http://localhost/test/result-notfound - 404 - application/json;+charset=utf-8 11.9658ms + Request finished HTTP/1.1 GET http://localhost/test/result-notfound - 404 - application/json;+charset=utf-8 7.2504ms info: Microsoft.AspNetCore.Hosting.Diagnostics[1] Request starting HTTP/1.1 GET http://localhost/test/result-failure - - - info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0] @@ -3978,11 +341,11 @@ info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[102] info: Microsoft.AspNetCore.Mvc.Infrastructure.ObjectResultExecutor[1] Executing ObjectResult, writing value of type 'ManagedCode.Communication.Result`1[[System.String, System.Private.CoreLib, Version=9.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]]'. info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[105] - Executed action ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.GetResultFailure (ManagedCode.Communication.Tests) in 0.9254ms + Executed action ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.GetResultFailure (ManagedCode.Communication.Tests) in 0.5116ms info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1] Executed endpoint 'ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.GetResultFailure (ManagedCode.Communication.Tests)' info: Microsoft.AspNetCore.Hosting.Diagnostics[2] - Request finished HTTP/1.1 GET http://localhost/test/result-failure - 400 - application/json;+charset=utf-8 2.7400ms + Request finished HTTP/1.1 GET http://localhost/test/result-failure - 400 - application/json;+charset=utf-8 1.2435ms info: Microsoft.AspNetCore.Hosting.Diagnostics[1] Request starting HTTP/1.1 GET http://localhost/test/result-success - - - info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0] @@ -3992,11 +355,11 @@ info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[102] info: Microsoft.AspNetCore.Mvc.Infrastructure.ObjectResultExecutor[1] Executing ObjectResult, writing value of type 'ManagedCode.Communication.Result`1[[ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestModel, ManagedCode.Communication.Tests, Version=9.6.2.0, Culture=neutral, PublicKeyToken=null]]'. info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[105] - Executed action ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.GetResultSuccessWithValue (ManagedCode.Communication.Tests) in 3.0901ms + Executed action ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.GetResultSuccessWithValue (ManagedCode.Communication.Tests) in 1.7778ms info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1] Executed endpoint 'ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.GetResultSuccessWithValue (ManagedCode.Communication.Tests)' info: Microsoft.AspNetCore.Hosting.Diagnostics[2] - Request finished HTTP/1.1 GET http://localhost/test/result-success - 200 - application/json;+charset=utf-8 4.6218ms + Request finished HTTP/1.1 GET http://localhost/test/result-success - 200 - application/json;+charset=utf-8 2.6297ms info: Microsoft.AspNetCore.Hosting.Diagnostics[1] Request starting HTTP/1.1 GET http://localhost/test/enum-error - - - info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0] @@ -4006,257 +369,12 @@ info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[102] info: Microsoft.AspNetCore.Mvc.Infrastructure.ObjectResultExecutor[1] Executing ObjectResult, writing value of type 'ManagedCode.Communication.Result`1[[System.String, System.Private.CoreLib, Version=9.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]]'. info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[105] - Executed action ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.GetEnumError (ManagedCode.Communication.Tests) in 0.6791ms + Executed action ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.GetEnumError (ManagedCode.Communication.Tests) in 0.8662ms info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1] Executed endpoint 'ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.GetEnumError (ManagedCode.Communication.Tests)' info: Microsoft.AspNetCore.Hosting.Diagnostics[2] - Request finished HTTP/1.1 GET http://localhost/test/enum-error - 400 - application/json;+charset=utf-8 2.1363ms -info: Microsoft.AspNetCore.Hosting.Diagnostics[1] - Request starting HTTP/1.1 GET http://localhost/test/result-success - - - -info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0] - Executing endpoint 'ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.GetResultSuccessWithValue (ManagedCode.Communication.Tests)' -info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[102] - Route matched with {action = "GetResultSuccessWithValue", controller = "Test"}. Executing controller action with signature ManagedCode.Communication.Result`1[ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestModel] GetResultSuccessWithValue() on controller ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController (ManagedCode.Communication.Tests). -info: Microsoft.AspNetCore.Mvc.Infrastructure.ObjectResultExecutor[1] - Executing ObjectResult, writing value of type 'ManagedCode.Communication.Result`1[[ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestModel, ManagedCode.Communication.Tests, Version=9.6.2.0, Culture=neutral, PublicKeyToken=null]]'. -info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[105] - Executed action ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.GetResultSuccessWithValue (ManagedCode.Communication.Tests) in 0.7359ms -info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1] - Executed endpoint 'ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.GetResultSuccessWithValue (ManagedCode.Communication.Tests)' -info: Microsoft.AspNetCore.Hosting.Diagnostics[2] - Request finished HTTP/1.1 GET http://localhost/test/result-success - 200 - application/json;+charset=utf-8 0.9482ms -info: Microsoft.AspNetCore.Hosting.Diagnostics[1] - Request starting HTTP/1.1 GET http://localhost/test/test2 - - - -info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0] - Executing endpoint 'ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.Test2 (ManagedCode.Communication.Tests)' -info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[102] - Route matched with {action = "Test2", controller = "Test"}. Executing controller action with signature Microsoft.AspNetCore.Mvc.ActionResult`1[System.String] Test2() on controller ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController (ManagedCode.Communication.Tests). -fail: ManagedCode.Communication.AspNetCore.Filters.CommunicationExceptionFilter[0] - Unhandled exception in Test.ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.Test2 (ManagedCode.Communication.Tests) - System.IO.InvalidDataException: InvalidDataException - at ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.Test2() in /_/ManagedCode.Communication.Tests/Common/TestApp/Controllers/TestController.cs:line 24 - at lambda_method119(Closure, Object, Object[]) - at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.SyncObjectResultExecutor.Execute(ActionContext actionContext, IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments) - at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeActionMethodAsync() - at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted) - at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeNextActionFilterAsync() - --- End of stack trace from previous location --- - at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context) - at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted) - at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeInnerFilterAsync() - --- End of stack trace from previous location --- - at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextExceptionFilterAsync>g__Awaited|26_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted) -info: ManagedCode.Communication.AspNetCore.Filters.CommunicationExceptionFilter[0] - Exception handled by CommunicationExceptionFilter for Test.ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.Test2 (ManagedCode.Communication.Tests) -info: Microsoft.AspNetCore.Mvc.Infrastructure.ObjectResultExecutor[1] - Executing ObjectResult, writing value of type 'ManagedCode.Communication.Result'. -info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[105] - Executed action ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.Test2 (ManagedCode.Communication.Tests) in 5.2306ms -info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1] - Executed endpoint 'ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.Test2 (ManagedCode.Communication.Tests)' -info: Microsoft.AspNetCore.Hosting.Diagnostics[2] - Request finished HTTP/1.1 GET http://localhost/test/test2 - 409 - application/json;+charset=utf-8 8.2771ms -info: Microsoft.AspNetCore.Hosting.Diagnostics[1] - Request starting HTTP/1.1 GET http://localhost/test/result-unauthorized - - - -info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0] - Executing endpoint 'ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.GetResultUnauthorized (ManagedCode.Communication.Tests)' -info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[102] - Route matched with {action = "GetResultUnauthorized", controller = "Test"}. Executing controller action with signature ManagedCode.Communication.Result GetResultUnauthorized() on controller ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController (ManagedCode.Communication.Tests). -info: Microsoft.AspNetCore.Mvc.Infrastructure.ObjectResultExecutor[1] - Executing ObjectResult, writing value of type 'ManagedCode.Communication.Result'. -info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[105] - Executed action ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.GetResultUnauthorized (ManagedCode.Communication.Tests) in 2.9514ms -info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1] - Executed endpoint 'ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.GetResultUnauthorized (ManagedCode.Communication.Tests)' -info: Microsoft.AspNetCore.Hosting.Diagnostics[2] - Request finished HTTP/1.1 GET http://localhost/test/result-unauthorized - 401 - application/json;+charset=utf-8 7.3584ms -info: Microsoft.AspNetCore.Hosting.Diagnostics[1] - Request starting HTTP/2 POST http://localhost/TestHub/negotiate?negotiateVersion=1 - - - -info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0] - Executing endpoint 'TestHub/negotiate' -info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1] - Executed endpoint 'TestHub/negotiate' -info: Microsoft.AspNetCore.Hosting.Diagnostics[2] - Request finished HTTP/2 POST http://localhost/TestHub/negotiate?negotiateVersion=1 - 200 316 application/json 14.9025ms -info: Microsoft.AspNetCore.Hosting.Diagnostics[1] - Request starting HTTP/2 POST http://localhost/TestHub/negotiate?negotiateVersion=1 - - - -info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0] - Executing endpoint 'TestHub/negotiate' -info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1] - Executed endpoint 'TestHub/negotiate' -info: Microsoft.AspNetCore.Hosting.Diagnostics[2] - Request finished HTTP/2 POST http://localhost/TestHub/negotiate?negotiateVersion=1 - 200 316 application/json 0.5284ms -info: Microsoft.AspNetCore.Hosting.Diagnostics[1] - Request starting HTTP/2 GET http://localhost/TestHub?id=_4PuJqwpTJLYR6v7gM6-ZQ - - - -info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0] - Executing endpoint 'TestHub' -info: Microsoft.AspNetCore.Hosting.Diagnostics[1] - Request starting HTTP/2 POST http://localhost/TestHub?id=_4PuJqwpTJLYR6v7gM6-ZQ - - 32 -info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0] - Executing endpoint 'TestHub' -info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1] - Executed endpoint 'TestHub' -info: Microsoft.AspNetCore.Hosting.Diagnostics[2] - Request finished HTTP/2 POST http://localhost/TestHub?id=_4PuJqwpTJLYR6v7gM6-ZQ - 200 - text/plain 1.8656ms -info: Microsoft.AspNetCore.Hosting.Diagnostics[1] - Request starting HTTP/2 POST http://localhost/TestHub?id=_4PuJqwpTJLYR6v7gM6-ZQ - - 11 -info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0] - Executing endpoint 'TestHub' -info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1] - Executed endpoint 'TestHub' -info: Microsoft.AspNetCore.Hosting.Diagnostics[2] - Request finished HTTP/2 POST http://localhost/TestHub?id=_4PuJqwpTJLYR6v7gM6-ZQ - 200 - text/plain 0.2666ms -info: Microsoft.AspNetCore.Hosting.Diagnostics[1] - Request starting HTTP/2 POST http://localhost/TestHub?id=_4PuJqwpTJLYR6v7gM6-ZQ - - 63 -info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0] - Executing endpoint 'TestHub' -info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1] - Executed endpoint 'TestHub' -info: Microsoft.AspNetCore.Hosting.Diagnostics[2] - Request finished HTTP/2 POST http://localhost/TestHub?id=_4PuJqwpTJLYR6v7gM6-ZQ - 200 - text/plain 0.2351ms -Current value: 10 -info: Microsoft.AspNetCore.Hosting.Diagnostics[1] - Request starting HTTP/1.1 GET http://localhost/test/result-fail - - - -info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0] - Executing endpoint 'ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.GetResultFail (ManagedCode.Communication.Tests)' -info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[102] - Route matched with {action = "GetResultFail", controller = "Test"}. Executing controller action with signature ManagedCode.Communication.Result GetResultFail() on controller ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController (ManagedCode.Communication.Tests). -info: Microsoft.AspNetCore.Mvc.Infrastructure.ObjectResultExecutor[1] - Executing ObjectResult, writing value of type 'ManagedCode.Communication.Result'. -info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[105] - Executed action ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.GetResultFail (ManagedCode.Communication.Tests) in 1.5711ms -info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1] - Executed endpoint 'ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.GetResultFail (ManagedCode.Communication.Tests)' -info: Microsoft.AspNetCore.Hosting.Diagnostics[2] - Request finished HTTP/1.1 GET http://localhost/test/result-fail - 400 - application/json;+charset=utf-8 3.3251ms -info: Microsoft.AspNetCore.Hosting.Diagnostics[1] - Request starting HTTP/1.1 GET http://localhost/test/result-not-found - - - -info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0] - Executing endpoint 'ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.GetResultNotFound (ManagedCode.Communication.Tests)' -info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[102] - Route matched with {action = "GetResultNotFound", controller = "Test"}. Executing controller action with signature ManagedCode.Communication.Result`1[System.String] GetResultNotFound() on controller ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController (ManagedCode.Communication.Tests). -info: Microsoft.AspNetCore.Mvc.Infrastructure.ObjectResultExecutor[1] - Executing ObjectResult, writing value of type 'ManagedCode.Communication.Result`1[[System.String, System.Private.CoreLib, Version=9.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]]'. -info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[105] - Executed action ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.GetResultNotFound (ManagedCode.Communication.Tests) in 1.3899ms -info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1] - Executed endpoint 'ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.GetResultNotFound (ManagedCode.Communication.Tests)' -info: Microsoft.AspNetCore.Hosting.Diagnostics[2] - Request finished HTTP/1.1 GET http://localhost/test/result-not-found - 404 - application/json;+charset=utf-8 2.9295ms -info: Microsoft.AspNetCore.Hosting.Diagnostics[1] - Request starting HTTP/1.1 GET http://localhost/test/test1 - - - -info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0] - Executing endpoint 'ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.Test1 (ManagedCode.Communication.Tests)' -info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[102] - Route matched with {action = "Test1", controller = "Test"}. Executing controller action with signature Microsoft.AspNetCore.Mvc.ActionResult`1[System.String] Test1() on controller ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController (ManagedCode.Communication.Tests). -fail: ManagedCode.Communication.AspNetCore.Filters.CommunicationExceptionFilter[0] - Unhandled exception in Test.ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.Test1 (ManagedCode.Communication.Tests) - System.ComponentModel.DataAnnotations.ValidationException: ValidationException - at ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.Test1() in /_/ManagedCode.Communication.Tests/Common/TestApp/Controllers/TestController.cs:line 18 - at lambda_method131(Closure, Object, Object[]) - at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.SyncObjectResultExecutor.Execute(ActionContext actionContext, IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments) - at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeActionMethodAsync() - at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted) - at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeNextActionFilterAsync() - --- End of stack trace from previous location --- - at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context) - at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted) - at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeInnerFilterAsync() - --- End of stack trace from previous location --- - at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextExceptionFilterAsync>g__Awaited|26_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted) -info: ManagedCode.Communication.AspNetCore.Filters.CommunicationExceptionFilter[0] - Exception handled by CommunicationExceptionFilter for Test.ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.Test1 (ManagedCode.Communication.Tests) -info: Microsoft.AspNetCore.Mvc.Infrastructure.ObjectResultExecutor[1] - Executing ObjectResult, writing value of type 'ManagedCode.Communication.Result'. -info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[105] - Executed action ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.Test1 (ManagedCode.Communication.Tests) in 1.2873ms -info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1] - Executed endpoint 'ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.Test1 (ManagedCode.Communication.Tests)' -info: Microsoft.AspNetCore.Hosting.Diagnostics[2] - Request finished HTTP/1.1 GET http://localhost/test/test1 - 500 - application/json;+charset=utf-8 2.1952ms -info: Microsoft.AspNetCore.Hosting.Diagnostics[1] - Request starting HTTP/2 POST http://localhost/TestHub/negotiate?negotiateVersion=1 - - - -info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0] - Executing endpoint 'TestHub/negotiate' -info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1] - Executed endpoint 'TestHub/negotiate' -info: Microsoft.AspNetCore.Hosting.Diagnostics[2] - Request finished HTTP/2 POST http://localhost/TestHub/negotiate?negotiateVersion=1 - 200 316 application/json 1.4226ms -info: Microsoft.AspNetCore.Hosting.Diagnostics[1] - Request starting HTTP/2 POST http://localhost/TestHub/negotiate?negotiateVersion=1 - - - -info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0] - Executing endpoint 'TestHub/negotiate' -info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1] - Executed endpoint 'TestHub/negotiate' -info: Microsoft.AspNetCore.Hosting.Diagnostics[2] - Request finished HTTP/2 POST http://localhost/TestHub/negotiate?negotiateVersion=1 - 200 316 application/json 1.1951ms -info: Microsoft.AspNetCore.Hosting.Diagnostics[1] - Request starting HTTP/2 GET http://localhost/TestHub?id=VT0JM46vYtZli6P_2sa7qA - - - -info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0] - Executing endpoint 'TestHub' -info: Microsoft.AspNetCore.Hosting.Diagnostics[1] - Request starting HTTP/2 POST http://localhost/TestHub?id=VT0JM46vYtZli6P_2sa7qA - - 32 -info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0] - Executing endpoint 'TestHub' -info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1] - Executed endpoint 'TestHub' -info: Microsoft.AspNetCore.Hosting.Diagnostics[2] - Request finished HTTP/2 POST http://localhost/TestHub?id=VT0JM46vYtZli6P_2sa7qA - 200 - text/plain 0.7343ms -info: Microsoft.AspNetCore.Hosting.Diagnostics[1] - Request starting HTTP/2 POST http://localhost/TestHub?id=VT0JM46vYtZli6P_2sa7qA - - 11 -info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0] - Executing endpoint 'TestHub' -info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1] - Executed endpoint 'TestHub' -info: Microsoft.AspNetCore.Hosting.Diagnostics[2] - Request finished HTTP/2 POST http://localhost/TestHub?id=VT0JM46vYtZli6P_2sa7qA - 200 - text/plain 0.3475ms -info: Microsoft.AspNetCore.Hosting.Diagnostics[1] - Request starting HTTP/2 POST http://localhost/TestHub?id=VT0JM46vYtZli6P_2sa7qA - - 62 -info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0] - Executing endpoint 'TestHub' -info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1] - Executed endpoint 'TestHub' -info: Microsoft.AspNetCore.Hosting.Diagnostics[2] - Request finished HTTP/2 POST http://localhost/TestHub?id=VT0JM46vYtZli6P_2sa7qA - 200 - text/plain 0.1432ms -fail: ManagedCode.Communication.AspNetCore.Filters.CommunicationHubExceptionFilter[0] - Unhandled exception in hub method TestHub.Throw - System.IO.InvalidDataException: InvalidDataException - at ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestHub.Throw() in /_/ManagedCode.Communication.Tests/Common/TestApp/Controllers/TestHub.cs:line 17 - at lambda_method85(Closure, Object, Object[]) - at Microsoft.AspNetCore.SignalR.Internal.DefaultHubDispatcher`1.ExecuteMethod(ObjectMethodExecutor methodExecutor, Hub hub, Object[] arguments) - at ManagedCode.Communication.AspNetCore.Filters.CommunicationHubExceptionFilter.InvokeMethodAsync(HubInvocationContext invocationContext, Func`2 next) in /_/ManagedCode.Communication.AspNetCore/SignalR/Filters/CommunicationHubExceptionFilter.cs:line 15 -info: Microsoft.AspNetCore.Hosting.Diagnostics[1] - Request starting HTTP/1.1 GET http://localhost/test/test3 - - - -info: Microsoft.AspNetCore.Authorization.DefaultAuthorizationService[2] - Authorization failed. These requirements were not met: - DenyAnonymousAuthorizationRequirement: Requires an authenticated user. -info: ManagedCode.Communication.Tests.Common.TestApp.TestAuthenticationHandler[12] - AuthenticationScheme: Test was challenged. -info: Microsoft.AspNetCore.Hosting.Diagnostics[2] - Request finished HTTP/1.1 GET http://localhost/test/test3 - 401 - - 4.2841ms -info: Microsoft.AspNetCore.Hosting.Diagnostics[1] - Request starting HTTP/1.1 GET http://localhost/test/result-forbidden - - - -info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0] - Executing endpoint 'ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.GetResultForbidden (ManagedCode.Communication.Tests)' -info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[102] - Route matched with {action = "GetResultForbidden", controller = "Test"}. Executing controller action with signature ManagedCode.Communication.Result GetResultForbidden() on controller ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController (ManagedCode.Communication.Tests). -info: Microsoft.AspNetCore.Mvc.Infrastructure.ObjectResultExecutor[1] - Executing ObjectResult, writing value of type 'ManagedCode.Communication.Result'. -info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[105] - Executed action ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.GetResultForbidden (ManagedCode.Communication.Tests) in 0.3604ms -info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1] - Executed endpoint 'ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.GetResultForbidden (ManagedCode.Communication.Tests)' -info: Microsoft.AspNetCore.Hosting.Diagnostics[2] - Request finished HTTP/1.1 GET http://localhost/test/result-forbidden - 403 - application/json;+charset=utf-8 2.2981ms -info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1] - Executed endpoint 'TestHub' -info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1] - Executed endpoint 'TestHub' -info: Microsoft.AspNetCore.Hosting.Diagnostics[2] - Request finished HTTP/2 GET http://localhost/TestHub?id=VT0JM46vYtZli6P_2sa7qA - 200 - text/event-stream 163.8080ms -info: Microsoft.AspNetCore.Hosting.Diagnostics[2] - Request finished HTTP/2 GET http://localhost/TestHub?id=_4PuJqwpTJLYR6v7gM6-ZQ - 200 - text/event-stream 534.1254ms -[xUnit.net 00:00:03.60] Finished: ManagedCode.Communication.Tests + Request finished HTTP/1.1 GET http://localhost/test/enum-error - 400 - application/json;+charset=utf-8 1.6213ms +[xUnit.net 00:00:02.02] Finished: ManagedCode.Communication.Tests diff --git a/dotnet-install.sh.1 b/dotnet-install.sh.1 new file mode 100644 index 0000000..034d2df --- /dev/null +++ b/dotnet-install.sh.1 @@ -0,0 +1,1888 @@ +#!/usr/bin/env bash +# Copyright (c) .NET Foundation and contributors. All rights reserved. +# Licensed under the MIT license. See LICENSE file in the project root for full license information. +# + +# Stop script on NZEC +set -e +# Stop script if unbound variable found (use ${var:-} if intentional) +set -u +# By default cmd1 | cmd2 returns exit code of cmd2 regardless of cmd1 success +# This is causing it to fail +set -o pipefail + +# Use in the the functions: eval $invocation +invocation='say_verbose "Calling: ${yellow:-}${FUNCNAME[0]} ${green:-}$*${normal:-}"' + +# standard output may be used as a return value in the functions +# we need a way to write text on the screen in the functions so that +# it won't interfere with the return value. +# Exposing stream 3 as a pipe to standard output of the script itself +exec 3>&1 + +# Setup some colors to use. These need to work in fairly limited shells, like the Ubuntu Docker container where there are only 8 colors. +# See if stdout is a terminal +if [ -t 1 ] && command -v tput > /dev/null; then + # see if it supports colors + ncolors=$(tput colors || echo 0) + if [ -n "$ncolors" ] && [ $ncolors -ge 8 ]; then + bold="$(tput bold || echo)" + normal="$(tput sgr0 || echo)" + black="$(tput setaf 0 || echo)" + red="$(tput setaf 1 || echo)" + green="$(tput setaf 2 || echo)" + yellow="$(tput setaf 3 || echo)" + blue="$(tput setaf 4 || echo)" + magenta="$(tput setaf 5 || echo)" + cyan="$(tput setaf 6 || echo)" + white="$(tput setaf 7 || echo)" + fi +fi + +say_warning() { + printf "%b\n" "${yellow:-}dotnet_install: Warning: $1${normal:-}" >&3 +} + +say_err() { + printf "%b\n" "${red:-}dotnet_install: Error: $1${normal:-}" >&2 +} + +say() { + # using stream 3 (defined in the beginning) to not interfere with stdout of functions + # which may be used as return value + printf "%b\n" "${cyan:-}dotnet-install:${normal:-} $1" >&3 +} + +say_verbose() { + if [ "$verbose" = true ]; then + say "$1" + fi +} + +# This platform list is finite - if the SDK/Runtime has supported Linux distribution-specific assets, +# then and only then should the Linux distribution appear in this list. +# Adding a Linux distribution to this list does not imply distribution-specific support. +get_legacy_os_name_from_platform() { + eval $invocation + + platform="$1" + case "$platform" in + "centos.7") + echo "centos" + return 0 + ;; + "debian.8") + echo "debian" + return 0 + ;; + "debian.9") + echo "debian.9" + return 0 + ;; + "fedora.23") + echo "fedora.23" + return 0 + ;; + "fedora.24") + echo "fedora.24" + return 0 + ;; + "fedora.27") + echo "fedora.27" + return 0 + ;; + "fedora.28") + echo "fedora.28" + return 0 + ;; + "opensuse.13.2") + echo "opensuse.13.2" + return 0 + ;; + "opensuse.42.1") + echo "opensuse.42.1" + return 0 + ;; + "opensuse.42.3") + echo "opensuse.42.3" + return 0 + ;; + "rhel.7"*) + echo "rhel" + return 0 + ;; + "ubuntu.14.04") + echo "ubuntu" + return 0 + ;; + "ubuntu.16.04") + echo "ubuntu.16.04" + return 0 + ;; + "ubuntu.16.10") + echo "ubuntu.16.10" + return 0 + ;; + "ubuntu.18.04") + echo "ubuntu.18.04" + return 0 + ;; + "alpine.3.4.3") + echo "alpine" + return 0 + ;; + esac + return 1 +} + +get_legacy_os_name() { + eval $invocation + + local uname=$(uname) + if [ "$uname" = "Darwin" ]; then + echo "osx" + return 0 + elif [ -n "$runtime_id" ]; then + echo $(get_legacy_os_name_from_platform "${runtime_id%-*}" || echo "${runtime_id%-*}") + return 0 + else + if [ -e /etc/os-release ]; then + . /etc/os-release + os=$(get_legacy_os_name_from_platform "$ID${VERSION_ID:+.${VERSION_ID}}" || echo "") + if [ -n "$os" ]; then + echo "$os" + return 0 + fi + fi + fi + + say_verbose "Distribution specific OS name and version could not be detected: UName = $uname" + return 1 +} + +get_linux_platform_name() { + eval $invocation + + if [ -n "$runtime_id" ]; then + echo "${runtime_id%-*}" + return 0 + else + if [ -e /etc/os-release ]; then + . /etc/os-release + echo "$ID${VERSION_ID:+.${VERSION_ID}}" + return 0 + elif [ -e /etc/redhat-release ]; then + local redhatRelease=$(&1 || true) | grep -q musl +} + +get_current_os_name() { + eval $invocation + + local uname=$(uname) + if [ "$uname" = "Darwin" ]; then + echo "osx" + return 0 + elif [ "$uname" = "FreeBSD" ]; then + echo "freebsd" + return 0 + elif [ "$uname" = "Linux" ]; then + local linux_platform_name="" + linux_platform_name="$(get_linux_platform_name)" || true + + if [ "$linux_platform_name" = "rhel.6" ]; then + echo $linux_platform_name + return 0 + elif is_musl_based_distro; then + echo "linux-musl" + return 0 + elif [ "$linux_platform_name" = "linux-musl" ]; then + echo "linux-musl" + return 0 + else + echo "linux" + return 0 + fi + fi + + say_err "OS name could not be detected: UName = $uname" + return 1 +} + +machine_has() { + eval $invocation + + command -v "$1" > /dev/null 2>&1 + return $? +} + +check_min_reqs() { + local hasMinimum=false + if machine_has "curl"; then + hasMinimum=true + elif machine_has "wget"; then + hasMinimum=true + fi + + if [ "$hasMinimum" = "false" ]; then + say_err "curl (recommended) or wget are required to download dotnet. Install missing prerequisite to proceed." + return 1 + fi + return 0 +} + +# args: +# input - $1 +to_lowercase() { + #eval $invocation + + echo "$1" | tr '[:upper:]' '[:lower:]' + return 0 +} + +# args: +# input - $1 +remove_trailing_slash() { + #eval $invocation + + local input="${1:-}" + echo "${input%/}" + return 0 +} + +# args: +# input - $1 +remove_beginning_slash() { + #eval $invocation + + local input="${1:-}" + echo "${input#/}" + return 0 +} + +# args: +# root_path - $1 +# child_path - $2 - this parameter can be empty +combine_paths() { + eval $invocation + + # TODO: Consider making it work with any number of paths. For now: + if [ ! -z "${3:-}" ]; then + say_err "combine_paths: Function takes two parameters." + return 1 + fi + + local root_path="$(remove_trailing_slash "$1")" + local child_path="$(remove_beginning_slash "${2:-}")" + say_verbose "combine_paths: root_path=$root_path" + say_verbose "combine_paths: child_path=$child_path" + echo "$root_path/$child_path" + return 0 +} + +get_machine_architecture() { + eval $invocation + + if command -v uname > /dev/null; then + CPUName=$(uname -m) + case $CPUName in + armv1*|armv2*|armv3*|armv4*|armv5*|armv6*) + echo "armv6-or-below" + return 0 + ;; + armv*l) + echo "arm" + return 0 + ;; + aarch64|arm64) + if [ "$(getconf LONG_BIT)" -lt 64 ]; then + # This is 32-bit OS running on 64-bit CPU (for example Raspberry Pi OS) + echo "arm" + return 0 + fi + echo "arm64" + return 0 + ;; + s390x) + echo "s390x" + return 0 + ;; + ppc64le) + echo "ppc64le" + return 0 + ;; + loongarch64) + echo "loongarch64" + return 0 + ;; + riscv64) + echo "riscv64" + return 0 + ;; + powerpc|ppc) + echo "ppc" + return 0 + ;; + esac + fi + + # Always default to 'x64' + echo "x64" + return 0 +} + +# args: +# architecture - $1 +get_normalized_architecture_from_architecture() { + eval $invocation + + local architecture="$(to_lowercase "$1")" + + if [[ $architecture == \ ]]; then + machine_architecture="$(get_machine_architecture)" + if [[ "$machine_architecture" == "armv6-or-below" ]]; then + say_err "Architecture \`$machine_architecture\` not supported. If you think this is a bug, report it at https://github.com/dotnet/install-scripts/issues" + return 1 + fi + + echo $machine_architecture + return 0 + fi + + case "$architecture" in + amd64|x64) + echo "x64" + return 0 + ;; + arm) + echo "arm" + return 0 + ;; + arm64) + echo "arm64" + return 0 + ;; + s390x) + echo "s390x" + return 0 + ;; + ppc64le) + echo "ppc64le" + return 0 + ;; + loongarch64) + echo "loongarch64" + return 0 + ;; + esac + + say_err "Architecture \`$architecture\` not supported. If you think this is a bug, report it at https://github.com/dotnet/install-scripts/issues" + return 1 +} + +# args: +# version - $1 +# channel - $2 +# architecture - $3 +get_normalized_architecture_for_specific_sdk_version() { + eval $invocation + + local is_version_support_arm64="$(is_arm64_supported "$1")" + local is_channel_support_arm64="$(is_arm64_supported "$2")" + local architecture="$3"; + local osname="$(get_current_os_name)" + + if [ "$osname" == "osx" ] && [ "$architecture" == "arm64" ] && { [ "$is_version_support_arm64" = false ] || [ "$is_channel_support_arm64" = false ]; }; then + #check if rosetta is installed + if [ "$(/usr/bin/pgrep oahd >/dev/null 2>&1;echo $?)" -eq 0 ]; then + say_verbose "Changing user architecture from '$architecture' to 'x64' because .NET SDKs prior to version 6.0 do not support arm64." + echo "x64" + return 0; + else + say_err "Architecture \`$architecture\` is not supported for .NET SDK version \`$version\`. Please install Rosetta to allow emulation of the \`$architecture\` .NET SDK on this platform" + return 1 + fi + fi + + echo "$architecture" + return 0 +} + +# args: +# version or channel - $1 +is_arm64_supported() { + # Extract the major version by splitting on the dot + major_version="${1%%.*}" + + # Check if the major version is a valid number and less than 6 + case "$major_version" in + [0-9]*) + if [ "$major_version" -lt 6 ]; then + echo false + return 0 + fi + ;; + esac + + echo true + return 0 +} + +# args: +# user_defined_os - $1 +get_normalized_os() { + eval $invocation + + local osname="$(to_lowercase "$1")" + if [ ! -z "$osname" ]; then + case "$osname" in + osx | freebsd | rhel.6 | linux-musl | linux) + echo "$osname" + return 0 + ;; + macos) + osname='osx' + echo "$osname" + return 0 + ;; + *) + say_err "'$user_defined_os' is not a supported value for --os option, supported values are: osx, macos, linux, linux-musl, freebsd, rhel.6. If you think this is a bug, report it at https://github.com/dotnet/install-scripts/issues." + return 1 + ;; + esac + else + osname="$(get_current_os_name)" || return 1 + fi + echo "$osname" + return 0 +} + +# args: +# quality - $1 +get_normalized_quality() { + eval $invocation + + local quality="$(to_lowercase "$1")" + if [ ! -z "$quality" ]; then + case "$quality" in + daily | preview) + echo "$quality" + return 0 + ;; + ga) + #ga quality is available without specifying quality, so normalizing it to empty + return 0 + ;; + *) + say_err "'$quality' is not a supported value for --quality option. Supported values are: daily, preview, ga. If you think this is a bug, report it at https://github.com/dotnet/install-scripts/issues." + return 1 + ;; + esac + fi + return 0 +} + +# args: +# channel - $1 +get_normalized_channel() { + eval $invocation + + local channel="$(to_lowercase "$1")" + + if [[ $channel == current ]]; then + say_warning 'Value "Current" is deprecated for -Channel option. Use "STS" instead.' + fi + + if [[ $channel == release/* ]]; then + say_warning 'Using branch name with -Channel option is no longer supported with newer releases. Use -Quality option with a channel in X.Y format instead.'; + fi + + if [ ! -z "$channel" ]; then + case "$channel" in + lts) + echo "LTS" + return 0 + ;; + sts) + echo "STS" + return 0 + ;; + current) + echo "STS" + return 0 + ;; + *) + echo "$channel" + return 0 + ;; + esac + fi + + return 0 +} + +# args: +# runtime - $1 +get_normalized_product() { + eval $invocation + + local product="" + local runtime="$(to_lowercase "$1")" + if [[ "$runtime" == "dotnet" ]]; then + product="dotnet-runtime" + elif [[ "$runtime" == "aspnetcore" ]]; then + product="aspnetcore-runtime" + elif [ -z "$runtime" ]; then + product="dotnet-sdk" + fi + echo "$product" + return 0 +} + +# The version text returned from the feeds is a 1-line or 2-line string: +# For the SDK and the dotnet runtime (2 lines): +# Line 1: # commit_hash +# Line 2: # 4-part version +# For the aspnetcore runtime (1 line): +# Line 1: # 4-part version + +# args: +# version_text - stdin +get_version_from_latestversion_file_content() { + eval $invocation + + cat | tail -n 1 | sed 's/\r$//' + return 0 +} + +# args: +# install_root - $1 +# relative_path_to_package - $2 +# specific_version - $3 +is_dotnet_package_installed() { + eval $invocation + + local install_root="$1" + local relative_path_to_package="$2" + local specific_version="${3//[$'\t\r\n']}" + + local dotnet_package_path="$(combine_paths "$(combine_paths "$install_root" "$relative_path_to_package")" "$specific_version")" + say_verbose "is_dotnet_package_installed: dotnet_package_path=$dotnet_package_path" + + if [ -d "$dotnet_package_path" ]; then + return 0 + else + return 1 + fi +} + +# args: +# downloaded file - $1 +# remote_file_size - $2 +validate_remote_local_file_sizes() +{ + eval $invocation + + local downloaded_file="$1" + local remote_file_size="$2" + local file_size='' + + if [[ "$OSTYPE" == "linux-gnu"* ]]; then + file_size="$(stat -c '%s' "$downloaded_file")" + elif [[ "$OSTYPE" == "darwin"* ]]; then + # hardcode in order to avoid conflicts with GNU stat + file_size="$(/usr/bin/stat -f '%z' "$downloaded_file")" + fi + + if [ -n "$file_size" ]; then + say "Downloaded file size is $file_size bytes." + + if [ -n "$remote_file_size" ] && [ -n "$file_size" ]; then + if [ "$remote_file_size" -ne "$file_size" ]; then + say "The remote and local file sizes are not equal. The remote file size is $remote_file_size bytes and the local size is $file_size bytes. The local package may be corrupted." + else + say "The remote and local file sizes are equal." + fi + fi + + else + say "Either downloaded or local package size can not be measured. One of them may be corrupted." + fi +} + +# args: +# azure_feed - $1 +# channel - $2 +# normalized_architecture - $3 +get_version_from_latestversion_file() { + eval $invocation + + local azure_feed="$1" + local channel="$2" + local normalized_architecture="$3" + + local version_file_url=null + if [[ "$runtime" == "dotnet" ]]; then + version_file_url="$azure_feed/Runtime/$channel/latest.version" + elif [[ "$runtime" == "aspnetcore" ]]; then + version_file_url="$azure_feed/aspnetcore/Runtime/$channel/latest.version" + elif [ -z "$runtime" ]; then + version_file_url="$azure_feed/Sdk/$channel/latest.version" + else + say_err "Invalid value for \$runtime" + return 1 + fi + say_verbose "get_version_from_latestversion_file: latest url: $version_file_url" + + download "$version_file_url" || return $? + return 0 +} + +# args: +# json_file - $1 +parse_globaljson_file_for_version() { + eval $invocation + + local json_file="$1" + if [ ! -f "$json_file" ]; then + say_err "Unable to find \`$json_file\`" + return 1 + fi + + sdk_section=$(cat $json_file | tr -d "\r" | awk '/"sdk"/,/}/') + if [ -z "$sdk_section" ]; then + say_err "Unable to parse the SDK node in \`$json_file\`" + return 1 + fi + + sdk_list=$(echo $sdk_section | awk -F"[{}]" '{print $2}') + sdk_list=${sdk_list//[\" ]/} + sdk_list=${sdk_list//,/$'\n'} + + local version_info="" + while read -r line; do + IFS=: + while read -r key value; do + if [[ "$key" == "version" ]]; then + version_info=$value + fi + done <<< "$line" + done <<< "$sdk_list" + if [ -z "$version_info" ]; then + say_err "Unable to find the SDK:version node in \`$json_file\`" + return 1 + fi + + unset IFS; + echo "$version_info" + return 0 +} + +# args: +# azure_feed - $1 +# channel - $2 +# normalized_architecture - $3 +# version - $4 +# json_file - $5 +get_specific_version_from_version() { + eval $invocation + + local azure_feed="$1" + local channel="$2" + local normalized_architecture="$3" + local version="$(to_lowercase "$4")" + local json_file="$5" + + if [ -z "$json_file" ]; then + if [[ "$version" == "latest" ]]; then + local version_info + version_info="$(get_version_from_latestversion_file "$azure_feed" "$channel" "$normalized_architecture" false)" || return 1 + say_verbose "get_specific_version_from_version: version_info=$version_info" + echo "$version_info" | get_version_from_latestversion_file_content + return 0 + else + echo "$version" + return 0 + fi + else + local version_info + version_info="$(parse_globaljson_file_for_version "$json_file")" || return 1 + echo "$version_info" + return 0 + fi +} + +# args: +# azure_feed - $1 +# channel - $2 +# normalized_architecture - $3 +# specific_version - $4 +# normalized_os - $5 +construct_download_link() { + eval $invocation + + local azure_feed="$1" + local channel="$2" + local normalized_architecture="$3" + local specific_version="${4//[$'\t\r\n']}" + local specific_product_version="$(get_specific_product_version "$1" "$4")" + local osname="$5" + + local download_link=null + if [[ "$runtime" == "dotnet" ]]; then + download_link="$azure_feed/Runtime/$specific_version/dotnet-runtime-$specific_product_version-$osname-$normalized_architecture.tar.gz" + elif [[ "$runtime" == "aspnetcore" ]]; then + download_link="$azure_feed/aspnetcore/Runtime/$specific_version/aspnetcore-runtime-$specific_product_version-$osname-$normalized_architecture.tar.gz" + elif [ -z "$runtime" ]; then + download_link="$azure_feed/Sdk/$specific_version/dotnet-sdk-$specific_product_version-$osname-$normalized_architecture.tar.gz" + else + return 1 + fi + + echo "$download_link" + return 0 +} + +# args: +# azure_feed - $1 +# specific_version - $2 +# download link - $3 (optional) +get_specific_product_version() { + # If we find a 'productVersion.txt' at the root of any folder, we'll use its contents + # to resolve the version of what's in the folder, superseding the specified version. + # if 'productVersion.txt' is missing but download link is already available, product version will be taken from download link + eval $invocation + + local azure_feed="$1" + local specific_version="${2//[$'\t\r\n']}" + local package_download_link="" + if [ $# -gt 2 ]; then + local package_download_link="$3" + fi + local specific_product_version=null + + # Try to get the version number, using the productVersion.txt file located next to the installer file. + local download_links=($(get_specific_product_version_url "$azure_feed" "$specific_version" true "$package_download_link") + $(get_specific_product_version_url "$azure_feed" "$specific_version" false "$package_download_link")) + + for download_link in "${download_links[@]}" + do + say_verbose "Checking for the existence of $download_link" + + if machine_has "curl" + then + if ! specific_product_version=$(curl -s --fail "${download_link}${feed_credential}" 2>&1); then + continue + else + echo "${specific_product_version//[$'\t\r\n']}" + return 0 + fi + + elif machine_has "wget" + then + specific_product_version=$(wget -qO- "${download_link}${feed_credential}" 2>&1) + if [ $? = 0 ]; then + echo "${specific_product_version//[$'\t\r\n']}" + return 0 + fi + fi + done + + # Getting the version number with productVersion.txt has failed. Try parsing the download link for a version number. + say_verbose "Failed to get the version using productVersion.txt file. Download link will be parsed instead." + specific_product_version="$(get_product_specific_version_from_download_link "$package_download_link" "$specific_version")" + echo "${specific_product_version//[$'\t\r\n']}" + return 0 +} + +# args: +# azure_feed - $1 +# specific_version - $2 +# is_flattened - $3 +# download link - $4 (optional) +get_specific_product_version_url() { + eval $invocation + + local azure_feed="$1" + local specific_version="$2" + local is_flattened="$3" + local package_download_link="" + if [ $# -gt 3 ]; then + local package_download_link="$4" + fi + + local pvFileName="productVersion.txt" + if [ "$is_flattened" = true ]; then + if [ -z "$runtime" ]; then + pvFileName="sdk-productVersion.txt" + elif [[ "$runtime" == "dotnet" ]]; then + pvFileName="runtime-productVersion.txt" + else + pvFileName="$runtime-productVersion.txt" + fi + fi + + local download_link=null + + if [ -z "$package_download_link" ]; then + if [[ "$runtime" == "dotnet" ]]; then + download_link="$azure_feed/Runtime/$specific_version/${pvFileName}" + elif [[ "$runtime" == "aspnetcore" ]]; then + download_link="$azure_feed/aspnetcore/Runtime/$specific_version/${pvFileName}" + elif [ -z "$runtime" ]; then + download_link="$azure_feed/Sdk/$specific_version/${pvFileName}" + else + return 1 + fi + else + download_link="${package_download_link%/*}/${pvFileName}" + fi + + say_verbose "Constructed productVersion link: $download_link" + echo "$download_link" + return 0 +} + +# args: +# download link - $1 +# specific version - $2 +get_product_specific_version_from_download_link() +{ + eval $invocation + + local download_link="$1" + local specific_version="$2" + local specific_product_version="" + + if [ -z "$download_link" ]; then + echo "$specific_version" + return 0 + fi + + #get filename + filename="${download_link##*/}" + + #product specific version follows the product name + #for filename 'dotnet-sdk-3.1.404-linux-x64.tar.gz': the product version is 3.1.404 + IFS='-' + read -ra filename_elems <<< "$filename" + count=${#filename_elems[@]} + if [[ "$count" -gt 2 ]]; then + specific_product_version="${filename_elems[2]}" + else + specific_product_version=$specific_version + fi + unset IFS; + echo "$specific_product_version" + return 0 +} + +# args: +# azure_feed - $1 +# channel - $2 +# normalized_architecture - $3 +# specific_version - $4 +construct_legacy_download_link() { + eval $invocation + + local azure_feed="$1" + local channel="$2" + local normalized_architecture="$3" + local specific_version="${4//[$'\t\r\n']}" + + local distro_specific_osname + distro_specific_osname="$(get_legacy_os_name)" || return 1 + + local legacy_download_link=null + if [[ "$runtime" == "dotnet" ]]; then + legacy_download_link="$azure_feed/Runtime/$specific_version/dotnet-$distro_specific_osname-$normalized_architecture.$specific_version.tar.gz" + elif [ -z "$runtime" ]; then + legacy_download_link="$azure_feed/Sdk/$specific_version/dotnet-dev-$distro_specific_osname-$normalized_architecture.$specific_version.tar.gz" + else + return 1 + fi + + echo "$legacy_download_link" + return 0 +} + +get_user_install_path() { + eval $invocation + + if [ ! -z "${DOTNET_INSTALL_DIR:-}" ]; then + echo "$DOTNET_INSTALL_DIR" + else + echo "$HOME/.dotnet" + fi + return 0 +} + +# args: +# install_dir - $1 +resolve_installation_path() { + eval $invocation + + local install_dir=$1 + if [ "$install_dir" = "" ]; then + local user_install_path="$(get_user_install_path)" + say_verbose "resolve_installation_path: user_install_path=$user_install_path" + echo "$user_install_path" + return 0 + fi + + echo "$install_dir" + return 0 +} + +# args: +# relative_or_absolute_path - $1 +get_absolute_path() { + eval $invocation + + local relative_or_absolute_path=$1 + echo "$(cd "$(dirname "$1")" && pwd -P)/$(basename "$1")" + return 0 +} + +# args: +# override - $1 (boolean, true or false) +get_cp_options() { + eval $invocation + + local override="$1" + local override_switch="" + + if [ "$override" = false ]; then + override_switch="-n" + + # create temporary files to check if 'cp -u' is supported + tmp_dir="$(mktemp -d)" + tmp_file="$tmp_dir/testfile" + tmp_file2="$tmp_dir/testfile2" + + touch "$tmp_file" + + # use -u instead of -n if it's available + if cp -u "$tmp_file" "$tmp_file2" 2>/dev/null; then + override_switch="-u" + fi + + # clean up + rm -f "$tmp_file" "$tmp_file2" + rm -rf "$tmp_dir" + fi + + echo "$override_switch" +} + +# args: +# input_files - stdin +# root_path - $1 +# out_path - $2 +# override - $3 +copy_files_or_dirs_from_list() { + eval $invocation + + local root_path="$(remove_trailing_slash "$1")" + local out_path="$(remove_trailing_slash "$2")" + local override="$3" + local override_switch="$(get_cp_options "$override")" + + cat | uniq | while read -r file_path; do + local path="$(remove_beginning_slash "${file_path#$root_path}")" + local target="$out_path/$path" + if [ "$override" = true ] || (! ([ -d "$target" ] || [ -e "$target" ])); then + mkdir -p "$out_path/$(dirname "$path")" + if [ -d "$target" ]; then + rm -rf "$target" + fi + cp -R $override_switch "$root_path/$path" "$target" + fi + done +} + +# args: +# zip_uri - $1 +get_remote_file_size() { + local zip_uri="$1" + + if machine_has "curl"; then + file_size=$(curl -sI "$zip_uri" | grep -i content-length | awk '{ num = $2 + 0; print num }') + elif machine_has "wget"; then + file_size=$(wget --spider --server-response -O /dev/null "$zip_uri" 2>&1 | grep -i 'Content-Length:' | awk '{ num = $2 + 0; print num }') + else + say "Neither curl nor wget is available on this system." + return + fi + + if [ -n "$file_size" ]; then + say "Remote file $zip_uri size is $file_size bytes." + echo "$file_size" + else + say_verbose "Content-Length header was not extracted for $zip_uri." + echo "" + fi +} + +# args: +# zip_path - $1 +# out_path - $2 +# remote_file_size - $3 +extract_dotnet_package() { + eval $invocation + + local zip_path="$1" + local out_path="$2" + local remote_file_size="$3" + + local temp_out_path="$(mktemp -d "$temporary_file_template")" + + local failed=false + tar -xzf "$zip_path" -C "$temp_out_path" > /dev/null || failed=true + + local folders_with_version_regex='^.*/[0-9]+\.[0-9]+[^/]+/' + find "$temp_out_path" -type f | grep -Eo "$folders_with_version_regex" | sort | copy_files_or_dirs_from_list "$temp_out_path" "$out_path" false + find "$temp_out_path" -type f | grep -Ev "$folders_with_version_regex" | copy_files_or_dirs_from_list "$temp_out_path" "$out_path" "$override_non_versioned_files" + + validate_remote_local_file_sizes "$zip_path" "$remote_file_size" + + rm -rf "$temp_out_path" + if [ -z ${keep_zip+x} ]; then + rm -f "$zip_path" && say_verbose "Temporary archive file $zip_path was removed" + fi + + if [ "$failed" = true ]; then + say_err "Extraction failed" + return 1 + fi + return 0 +} + +# args: +# remote_path - $1 +# disable_feed_credential - $2 +get_http_header() +{ + eval $invocation + local remote_path="$1" + local disable_feed_credential="$2" + + local failed=false + local response + if machine_has "curl"; then + get_http_header_curl $remote_path $disable_feed_credential || failed=true + elif machine_has "wget"; then + get_http_header_wget $remote_path $disable_feed_credential || failed=true + else + failed=true + fi + if [ "$failed" = true ]; then + say_verbose "Failed to get HTTP header: '$remote_path'." + return 1 + fi + return 0 +} + +# args: +# remote_path - $1 +# disable_feed_credential - $2 +get_http_header_curl() { + eval $invocation + local remote_path="$1" + local disable_feed_credential="$2" + + remote_path_with_credential="$remote_path" + if [ "$disable_feed_credential" = false ]; then + remote_path_with_credential+="$feed_credential" + fi + + curl_options="-I -sSL --retry 5 --retry-delay 2 --connect-timeout 15 " + curl $curl_options "$remote_path_with_credential" 2>&1 || return 1 + return 0 +} + +# args: +# remote_path - $1 +# disable_feed_credential - $2 +get_http_header_wget() { + eval $invocation + local remote_path="$1" + local disable_feed_credential="$2" + local wget_options="-q -S --spider --tries 5 " + + local wget_options_extra='' + + # Test for options that aren't supported on all wget implementations. + if [[ $(wget -h 2>&1 | grep -E 'waitretry|connect-timeout') ]]; then + wget_options_extra="--waitretry 2 --connect-timeout 15 " + else + say "wget extra options are unavailable for this environment" + fi + + remote_path_with_credential="$remote_path" + if [ "$disable_feed_credential" = false ]; then + remote_path_with_credential+="$feed_credential" + fi + + wget $wget_options $wget_options_extra "$remote_path_with_credential" 2>&1 + + return $? +} + +# args: +# remote_path - $1 +# [out_path] - $2 - stdout if not provided +download() { + eval $invocation + + local remote_path="$1" + local out_path="${2:-}" + + if [[ "$remote_path" != "http"* ]]; then + cp "$remote_path" "$out_path" + return $? + fi + + local failed=false + local attempts=0 + while [ $attempts -lt 3 ]; do + attempts=$((attempts+1)) + failed=false + if machine_has "curl"; then + downloadcurl "$remote_path" "$out_path" || failed=true + elif machine_has "wget"; then + downloadwget "$remote_path" "$out_path" || failed=true + else + say_err "Missing dependency: neither curl nor wget was found." + exit 1 + fi + + if [ "$failed" = false ] || [ $attempts -ge 3 ] || { [ ! -z $http_code ] && [ $http_code = "404" ]; }; then + break + fi + + say "Download attempt #$attempts has failed: $http_code $download_error_msg" + say "Attempt #$((attempts+1)) will start in $((attempts*10)) seconds." + sleep $((attempts*10)) + done + + if [ "$failed" = true ]; then + say_verbose "Download failed: $remote_path" + return 1 + fi + return 0 +} + +# Updates global variables $http_code and $download_error_msg +downloadcurl() { + eval $invocation + unset http_code + unset download_error_msg + local remote_path="$1" + local out_path="${2:-}" + # Append feed_credential as late as possible before calling curl to avoid logging feed_credential + # Avoid passing URI with credentials to functions: note, most of them echoing parameters of invocation in verbose output. + local remote_path_with_credential="${remote_path}${feed_credential}" + local curl_options="--retry 20 --retry-delay 2 --connect-timeout 15 -sSL -f --create-dirs " + local curl_exit_code=0; + if [ -z "$out_path" ]; then + curl_output=$(curl $curl_options "$remote_path_with_credential" 2>&1) + curl_exit_code=$? + echo "$curl_output" + else + curl_output=$(curl $curl_options -o "$out_path" "$remote_path_with_credential" 2>&1) + curl_exit_code=$? + fi + + # Regression in curl causes curl with --retry to return a 0 exit code even when it fails to download a file - https://github.com/curl/curl/issues/17554 + if [ $curl_exit_code -eq 0 ] && echo "$curl_output" | grep -q "^curl: ([0-9]*) "; then + curl_exit_code=$(echo "$curl_output" | sed 's/curl: (\([0-9]*\)).*/\1/') + fi + + if [ $curl_exit_code -gt 0 ]; then + download_error_msg="Unable to download $remote_path." + # Check for curl timeout codes + if [[ $curl_exit_code == 7 || $curl_exit_code == 28 ]]; then + download_error_msg+=" Failed to reach the server: connection timeout." + else + local disable_feed_credential=false + local response=$(get_http_header_curl $remote_path $disable_feed_credential) + http_code=$( echo "$response" | awk '/^HTTP/{print $2}' | tail -1 ) + if [[ ! -z $http_code && $http_code != 2* ]]; then + download_error_msg+=" Returned HTTP status code: $http_code." + fi + fi + say_verbose "$download_error_msg" + return 1 + fi + return 0 +} + + +# Updates global variables $http_code and $download_error_msg +downloadwget() { + eval $invocation + unset http_code + unset download_error_msg + local remote_path="$1" + local out_path="${2:-}" + # Append feed_credential as late as possible before calling wget to avoid logging feed_credential + local remote_path_with_credential="${remote_path}${feed_credential}" + local wget_options="--tries 20 " + + local wget_options_extra='' + local wget_result='' + + # Test for options that aren't supported on all wget implementations. + if [[ $(wget -h 2>&1 | grep -E 'waitretry|connect-timeout') ]]; then + wget_options_extra="--waitretry 2 --connect-timeout 15 " + else + say "wget extra options are unavailable for this environment" + fi + + if [ -z "$out_path" ]; then + wget -q $wget_options $wget_options_extra -O - "$remote_path_with_credential" 2>&1 + wget_result=$? + else + wget $wget_options $wget_options_extra -O "$out_path" "$remote_path_with_credential" 2>&1 + wget_result=$? + fi + + if [[ $wget_result != 0 ]]; then + local disable_feed_credential=false + local response=$(get_http_header_wget $remote_path $disable_feed_credential) + http_code=$( echo "$response" | awk '/^ HTTP/{print $2}' | tail -1 ) + download_error_msg="Unable to download $remote_path." + if [[ ! -z $http_code && $http_code != 2* ]]; then + download_error_msg+=" Returned HTTP status code: $http_code." + # wget exit code 4 stands for network-issue + elif [[ $wget_result == 4 ]]; then + download_error_msg+=" Failed to reach the server: connection timeout." + fi + say_verbose "$download_error_msg" + return 1 + fi + + return 0 +} + +get_download_link_from_aka_ms() { + eval $invocation + + #quality is not supported for LTS or STS channel + #STS maps to current + if [[ ! -z "$normalized_quality" && ("$normalized_channel" == "LTS" || "$normalized_channel" == "STS") ]]; then + normalized_quality="" + say_warning "Specifying quality for STS or LTS channel is not supported, the quality will be ignored." + fi + + say_verbose "Retrieving primary payload URL from aka.ms for channel: '$normalized_channel', quality: '$normalized_quality', product: '$normalized_product', os: '$normalized_os', architecture: '$normalized_architecture'." + + #construct aka.ms link + aka_ms_link="https://aka.ms/dotnet" + if [ "$internal" = true ]; then + aka_ms_link="$aka_ms_link/internal" + fi + aka_ms_link="$aka_ms_link/$normalized_channel" + if [[ ! -z "$normalized_quality" ]]; then + aka_ms_link="$aka_ms_link/$normalized_quality" + fi + aka_ms_link="$aka_ms_link/$normalized_product-$normalized_os-$normalized_architecture.tar.gz" + say_verbose "Constructed aka.ms link: '$aka_ms_link'." + + #get HTTP response + #do not pass credentials as a part of the $aka_ms_link and do not apply credentials in the get_http_header function + #otherwise the redirect link would have credentials as well + #it would result in applying credentials twice to the resulting link and thus breaking it, and in echoing credentials to the output as a part of redirect link + disable_feed_credential=true + response="$(get_http_header $aka_ms_link $disable_feed_credential)" + + say_verbose "Received response: $response" + # Get results of all the redirects. + http_codes=$( echo "$response" | awk '$1 ~ /^HTTP/ {print $2}' ) + # They all need to be 301, otherwise some links are broken (except for the last, which is not a redirect but 200 or 404). + broken_redirects=$( echo "$http_codes" | sed '$d' | grep -v '301' ) + # The response may end without final code 2xx/4xx/5xx somehow, e.g. network restrictions on www.bing.com causes redirecting to bing.com fails with connection refused. + # In this case it should not exclude the last. + last_http_code=$( echo "$http_codes" | tail -n 1 ) + if ! [[ $last_http_code =~ ^(2|4|5)[0-9][0-9]$ ]]; then + broken_redirects=$( echo "$http_codes" | grep -v '301' ) + fi + + # All HTTP codes are 301 (Moved Permanently), the redirect link exists. + if [[ -z "$broken_redirects" ]]; then + aka_ms_download_link=$( echo "$response" | awk '$1 ~ /^Location/{print $2}' | tail -1 | tr -d '\r') + + if [[ -z "$aka_ms_download_link" ]]; then + say_verbose "The aka.ms link '$aka_ms_link' is not valid: failed to get redirect location." + return 1 + fi + + say_verbose "The redirect location retrieved: '$aka_ms_download_link'." + return 0 + else + say_verbose "The aka.ms link '$aka_ms_link' is not valid: received HTTP code: $(echo "$broken_redirects" | paste -sd "," -)." + return 1 + fi +} + +get_feeds_to_use() +{ + feeds=( + "https://builds.dotnet.microsoft.com/dotnet" + "https://ci.dot.net/public" + ) + + if [[ -n "$azure_feed" ]]; then + feeds=("$azure_feed") + fi + + if [[ -n "$uncached_feed" ]]; then + feeds=("$uncached_feed") + fi +} + +# THIS FUNCTION MAY EXIT (if the determined version is already installed). +generate_download_links() { + + download_links=() + specific_versions=() + effective_versions=() + link_types=() + + # If generate_akams_links returns false, no fallback to old links. Just terminate. + # This function may also 'exit' (if the determined version is already installed). + generate_akams_links || return + + # Check other feeds only if we haven't been able to find an aka.ms link. + if [[ "${#download_links[@]}" -lt 1 ]]; then + for feed in ${feeds[@]} + do + # generate_regular_links may also 'exit' (if the determined version is already installed). + generate_regular_links $feed || return + done + fi + + if [[ "${#download_links[@]}" -eq 0 ]]; then + say_err "Failed to resolve the exact version number." + return 1 + fi + + say_verbose "Generated ${#download_links[@]} links." + for link_index in ${!download_links[@]} + do + say_verbose "Link $link_index: ${link_types[$link_index]}, ${effective_versions[$link_index]}, ${download_links[$link_index]}" + done +} + +# THIS FUNCTION MAY EXIT (if the determined version is already installed). +generate_akams_links() { + local valid_aka_ms_link=true; + + normalized_version="$(to_lowercase "$version")" + if [[ "$normalized_version" != "latest" ]] && [ -n "$normalized_quality" ]; then + say_err "Quality and Version options are not allowed to be specified simultaneously. See https://learn.microsoft.com/dotnet/core/tools/dotnet-install-script#options for details." + return 1 + fi + + if [[ -n "$json_file" || "$normalized_version" != "latest" ]]; then + # aka.ms links are not needed when exact version is specified via command or json file + return + fi + + get_download_link_from_aka_ms || valid_aka_ms_link=false + + if [[ "$valid_aka_ms_link" == true ]]; then + say_verbose "Retrieved primary payload URL from aka.ms link: '$aka_ms_download_link'." + say_verbose "Downloading using legacy url will not be attempted." + + download_link=$aka_ms_download_link + + #get version from the path + IFS='/' + read -ra pathElems <<< "$download_link" + count=${#pathElems[@]} + specific_version="${pathElems[count-2]}" + unset IFS; + say_verbose "Version: '$specific_version'." + + #Retrieve effective version + effective_version="$(get_specific_product_version "$azure_feed" "$specific_version" "$download_link")" + + # Add link info to arrays + download_links+=($download_link) + specific_versions+=($specific_version) + effective_versions+=($effective_version) + link_types+=("aka.ms") + + # Check if the SDK version is already installed. + if [[ "$dry_run" != true ]] && is_dotnet_package_installed "$install_root" "$asset_relative_path" "$effective_version"; then + say "$asset_name with version '$effective_version' is already installed." + exit 0 + fi + + return 0 + fi + + # if quality is specified - exit with error - there is no fallback approach + if [ ! -z "$normalized_quality" ]; then + say_err "Failed to locate the latest version in the channel '$normalized_channel' with '$normalized_quality' quality for '$normalized_product', os: '$normalized_os', architecture: '$normalized_architecture'." + say_err "Refer to: https://aka.ms/dotnet-os-lifecycle for information on .NET Core support." + return 1 + fi + say_verbose "Falling back to latest.version file approach." +} + +# THIS FUNCTION MAY EXIT (if the determined version is already installed) +# args: +# feed - $1 +generate_regular_links() { + local feed="$1" + local valid_legacy_download_link=true + + specific_version=$(get_specific_version_from_version "$feed" "$channel" "$normalized_architecture" "$version" "$json_file") || specific_version='0' + + if [[ "$specific_version" == '0' ]]; then + say_verbose "Failed to resolve the specific version number using feed '$feed'" + return + fi + + effective_version="$(get_specific_product_version "$feed" "$specific_version")" + say_verbose "specific_version=$specific_version" + + download_link="$(construct_download_link "$feed" "$channel" "$normalized_architecture" "$specific_version" "$normalized_os")" + say_verbose "Constructed primary named payload URL: $download_link" + + # Add link info to arrays + download_links+=($download_link) + specific_versions+=($specific_version) + effective_versions+=($effective_version) + link_types+=("primary") + + legacy_download_link="$(construct_legacy_download_link "$feed" "$channel" "$normalized_architecture" "$specific_version")" || valid_legacy_download_link=false + + if [ "$valid_legacy_download_link" = true ]; then + say_verbose "Constructed legacy named payload URL: $legacy_download_link" + + download_links+=($legacy_download_link) + specific_versions+=($specific_version) + effective_versions+=($effective_version) + link_types+=("legacy") + else + legacy_download_link="" + say_verbose "Could not construct a legacy_download_link; omitting..." + fi + + # Check if the SDK version is already installed. + if [[ "$dry_run" != true ]] && is_dotnet_package_installed "$install_root" "$asset_relative_path" "$effective_version"; then + say "$asset_name with version '$effective_version' is already installed." + exit 0 + fi +} + +print_dry_run() { + + say "Payload URLs:" + + for link_index in "${!download_links[@]}" + do + say "URL #$link_index - ${link_types[$link_index]}: ${download_links[$link_index]}" + done + + resolved_version=${specific_versions[0]} + repeatable_command="./$script_name --version "\""$resolved_version"\"" --install-dir "\""$install_root"\"" --architecture "\""$normalized_architecture"\"" --os "\""$normalized_os"\""" + + if [ ! -z "$normalized_quality" ]; then + repeatable_command+=" --quality "\""$normalized_quality"\""" + fi + + if [[ "$runtime" == "dotnet" ]]; then + repeatable_command+=" --runtime "\""dotnet"\""" + elif [[ "$runtime" == "aspnetcore" ]]; then + repeatable_command+=" --runtime "\""aspnetcore"\""" + fi + + repeatable_command+="$non_dynamic_parameters" + + if [ -n "$feed_credential" ]; then + repeatable_command+=" --feed-credential "\"""\""" + fi + + say "Repeatable invocation: $repeatable_command" +} + +calculate_vars() { + eval $invocation + + script_name=$(basename "$0") + normalized_architecture="$(get_normalized_architecture_from_architecture "$architecture")" + say_verbose "Normalized architecture: '$normalized_architecture'." + normalized_os="$(get_normalized_os "$user_defined_os")" + say_verbose "Normalized OS: '$normalized_os'." + normalized_quality="$(get_normalized_quality "$quality")" + say_verbose "Normalized quality: '$normalized_quality'." + normalized_channel="$(get_normalized_channel "$channel")" + say_verbose "Normalized channel: '$normalized_channel'." + normalized_product="$(get_normalized_product "$runtime")" + say_verbose "Normalized product: '$normalized_product'." + install_root="$(resolve_installation_path "$install_dir")" + say_verbose "InstallRoot: '$install_root'." + + normalized_architecture="$(get_normalized_architecture_for_specific_sdk_version "$version" "$normalized_channel" "$normalized_architecture")" + + if [[ "$runtime" == "dotnet" ]]; then + asset_relative_path="shared/Microsoft.NETCore.App" + asset_name=".NET Core Runtime" + elif [[ "$runtime" == "aspnetcore" ]]; then + asset_relative_path="shared/Microsoft.AspNetCore.App" + asset_name="ASP.NET Core Runtime" + elif [ -z "$runtime" ]; then + asset_relative_path="sdk" + asset_name=".NET Core SDK" + fi + + get_feeds_to_use +} + +install_dotnet() { + eval $invocation + local download_failed=false + local download_completed=false + local remote_file_size=0 + + mkdir -p "$install_root" + zip_path="${zip_path:-$(mktemp "$temporary_file_template")}" + say_verbose "Archive path: $zip_path" + + for link_index in "${!download_links[@]}" + do + download_link="${download_links[$link_index]}" + specific_version="${specific_versions[$link_index]}" + effective_version="${effective_versions[$link_index]}" + link_type="${link_types[$link_index]}" + + say "Attempting to download using $link_type link $download_link" + + # The download function will set variables $http_code and $download_error_msg in case of failure. + download_failed=false + download "$download_link" "$zip_path" 2>&1 || download_failed=true + + if [ "$download_failed" = true ]; then + case $http_code in + 404) + say "The resource at $link_type link '$download_link' is not available." + ;; + *) + say "Failed to download $link_type link '$download_link': $http_code $download_error_msg" + ;; + esac + rm -f "$zip_path" 2>&1 && say_verbose "Temporary archive file $zip_path was removed" + else + download_completed=true + break + fi + done + + if [[ "$download_completed" == false ]]; then + say_err "Could not find \`$asset_name\` with version = $specific_version" + say_err "Refer to: https://aka.ms/dotnet-os-lifecycle for information on .NET Core support" + return 1 + fi + + remote_file_size="$(get_remote_file_size "$download_link")" + + say "Extracting archive from $download_link" + extract_dotnet_package "$zip_path" "$install_root" "$remote_file_size" || return 1 + + # Check if the SDK version is installed; if not, fail the installation. + # if the version contains "RTM" or "servicing"; check if a 'release-type' SDK version is installed. + if [[ $specific_version == *"rtm"* || $specific_version == *"servicing"* ]]; then + IFS='-' + read -ra verArr <<< "$specific_version" + release_version="${verArr[0]}" + unset IFS; + say_verbose "Checking installation: version = $release_version" + if is_dotnet_package_installed "$install_root" "$asset_relative_path" "$release_version"; then + say "Installed version is $effective_version" + return 0 + fi + fi + + # Check if the standard SDK version is installed. + say_verbose "Checking installation: version = $effective_version" + if is_dotnet_package_installed "$install_root" "$asset_relative_path" "$effective_version"; then + say "Installed version is $effective_version" + return 0 + fi + + # Version verification failed. More likely something is wrong either with the downloaded content or with the verification algorithm. + say_err "Failed to verify the version of installed \`$asset_name\`.\nInstallation source: $download_link.\nInstallation location: $install_root.\nReport the bug at https://github.com/dotnet/install-scripts/issues." + say_err "\`$asset_name\` with version = $effective_version failed to install with an error." + return 1 +} + +args=("$@") + +local_version_file_relative_path="/.version" +bin_folder_relative_path="" +temporary_file_template="${TMPDIR:-/tmp}/dotnet.XXXXXXXXX" + +channel="LTS" +version="Latest" +json_file="" +install_dir="" +architecture="" +dry_run=false +no_path=false +azure_feed="" +uncached_feed="" +feed_credential="" +verbose=false +runtime="" +runtime_id="" +quality="" +internal=false +override_non_versioned_files=true +non_dynamic_parameters="" +user_defined_os="" + +while [ $# -ne 0 ] +do + name="$1" + case "$name" in + -c|--channel|-[Cc]hannel) + shift + channel="$1" + ;; + -v|--version|-[Vv]ersion) + shift + version="$1" + ;; + -q|--quality|-[Qq]uality) + shift + quality="$1" + ;; + --internal|-[Ii]nternal) + internal=true + non_dynamic_parameters+=" $name" + ;; + -i|--install-dir|-[Ii]nstall[Dd]ir) + shift + install_dir="$1" + ;; + --arch|--architecture|-[Aa]rch|-[Aa]rchitecture) + shift + architecture="$1" + ;; + --os|-[Oo][SS]) + shift + user_defined_os="$1" + ;; + --shared-runtime|-[Ss]hared[Rr]untime) + say_warning "The --shared-runtime flag is obsolete and may be removed in a future version of this script. The recommended usage is to specify '--runtime dotnet'." + if [ -z "$runtime" ]; then + runtime="dotnet" + fi + ;; + --runtime|-[Rr]untime) + shift + runtime="$1" + if [[ "$runtime" != "dotnet" ]] && [[ "$runtime" != "aspnetcore" ]]; then + say_err "Unsupported value for --runtime: '$1'. Valid values are 'dotnet' and 'aspnetcore'." + if [[ "$runtime" == "windowsdesktop" ]]; then + say_err "WindowsDesktop archives are manufactured for Windows platforms only." + fi + exit 1 + fi + ;; + --dry-run|-[Dd]ry[Rr]un) + dry_run=true + ;; + --no-path|-[Nn]o[Pp]ath) + no_path=true + non_dynamic_parameters+=" $name" + ;; + --verbose|-[Vv]erbose) + verbose=true + non_dynamic_parameters+=" $name" + ;; + --azure-feed|-[Aa]zure[Ff]eed) + shift + azure_feed="$1" + non_dynamic_parameters+=" $name "\""$1"\""" + ;; + --uncached-feed|-[Uu]ncached[Ff]eed) + shift + uncached_feed="$1" + non_dynamic_parameters+=" $name "\""$1"\""" + ;; + --feed-credential|-[Ff]eed[Cc]redential) + shift + feed_credential="$1" + #feed_credential should start with "?", for it to be added to the end of the link. + #adding "?" at the beginning of the feed_credential if needed. + [[ -z "$(echo $feed_credential)" ]] || [[ $feed_credential == \?* ]] || feed_credential="?$feed_credential" + ;; + --runtime-id|-[Rr]untime[Ii]d) + shift + runtime_id="$1" + non_dynamic_parameters+=" $name "\""$1"\""" + say_warning "Use of --runtime-id is obsolete and should be limited to the versions below 2.1. To override architecture, use --architecture option instead. To override OS, use --os option instead." + ;; + --jsonfile|-[Jj][Ss]on[Ff]ile) + shift + json_file="$1" + ;; + --skip-non-versioned-files|-[Ss]kip[Nn]on[Vv]ersioned[Ff]iles) + override_non_versioned_files=false + non_dynamic_parameters+=" $name" + ;; + --keep-zip|-[Kk]eep[Zz]ip) + keep_zip=true + non_dynamic_parameters+=" $name" + ;; + --zip-path|-[Zz]ip[Pp]ath) + shift + zip_path="$1" + ;; + -?|--?|-h|--help|-[Hh]elp) + script_name="dotnet-install.sh" + echo ".NET Tools Installer" + echo "Usage:" + echo " # Install a .NET SDK of a given Quality from a given Channel" + echo " $script_name [-c|--channel ] [-q|--quality ]" + echo " # Install a .NET SDK of a specific public version" + echo " $script_name [-v|--version ]" + echo " $script_name -h|-?|--help" + echo "" + echo "$script_name is a simple command line interface for obtaining dotnet cli." + echo " Note that the intended use of this script is for Continuous Integration (CI) scenarios, where:" + echo " - The SDK needs to be installed without user interaction and without admin rights." + echo " - The SDK installation doesn't need to persist across multiple CI runs." + echo " To set up a development environment or to run apps, use installers rather than this script. Visit https://dotnet.microsoft.com/download to get the installer." + echo "" + echo "Options:" + echo " -c,--channel Download from the channel specified, Defaults to \`$channel\`." + echo " -Channel" + echo " Possible values:" + echo " - STS - the most recent Standard Term Support release" + echo " - LTS - the most recent Long Term Support release" + echo " - 2-part version in a format A.B - represents a specific release" + echo " examples: 2.0; 1.0" + echo " - 3-part version in a format A.B.Cxx - represents a specific SDK release" + echo " examples: 5.0.1xx, 5.0.2xx." + echo " Supported since 5.0 release" + echo " Warning: Value 'Current' is deprecated for the Channel parameter. Use 'STS' instead." + echo " Note: The version parameter overrides the channel parameter when any version other than 'latest' is used." + echo " -v,--version Use specific VERSION, Defaults to \`$version\`." + echo " -Version" + echo " Possible values:" + echo " - latest - the latest build on specific channel" + echo " - 3-part version in a format A.B.C - represents specific version of build" + echo " examples: 2.0.0-preview2-006120; 1.1.0" + echo " -q,--quality Download the latest build of specified quality in the channel." + echo " -Quality" + echo " The possible values are: daily, preview, GA." + echo " Works only in combination with channel. Not applicable for STS and LTS channels and will be ignored if those channels are used." + echo " For SDK use channel in A.B.Cxx format. Using quality for SDK together with channel in A.B format is not supported." + echo " Supported since 5.0 release." + echo " Note: The version parameter overrides the channel parameter when any version other than 'latest' is used, and therefore overrides the quality." + echo " --internal,-Internal Download internal builds. Requires providing credentials via --feed-credential parameter." + echo " --feed-credential Token to access Azure feed. Used as a query string to append to the Azure feed." + echo " -FeedCredential This parameter typically is not specified." + echo " -i,--install-dir Install under specified location (see Install Location below)" + echo " -InstallDir" + echo " --architecture Architecture of dotnet binaries to be installed, Defaults to \`$architecture\`." + echo " --arch,-Architecture,-Arch" + echo " Possible values: x64, arm, arm64, s390x, ppc64le and loongarch64" + echo " --os Specifies operating system to be used when selecting the installer." + echo " Overrides the OS determination approach used by the script. Supported values: osx, linux, linux-musl, freebsd, rhel.6." + echo " In case any other value is provided, the platform will be determined by the script based on machine configuration." + echo " Not supported for legacy links. Use --runtime-id to specify platform for legacy links." + echo " Refer to: https://aka.ms/dotnet-os-lifecycle for more information." + echo " --runtime Installs a shared runtime only, without the SDK." + echo " -Runtime" + echo " Possible values:" + echo " - dotnet - the Microsoft.NETCore.App shared runtime" + echo " - aspnetcore - the Microsoft.AspNetCore.App shared runtime" + echo " --dry-run,-DryRun Do not perform installation. Display download link." + echo " --no-path, -NoPath Do not set PATH for the current process." + echo " --verbose,-Verbose Display diagnostics information." + echo " --azure-feed,-AzureFeed For internal use only." + echo " Allows using a different storage to download SDK archives from." + echo " --uncached-feed,-UncachedFeed For internal use only." + echo " Allows using a different storage to download SDK archives from." + echo " --skip-non-versioned-files Skips non-versioned files if they already exist, such as the dotnet executable." + echo " -SkipNonVersionedFiles" + echo " --jsonfile Determines the SDK version from a user specified global.json file." + echo " Note: global.json must have a value for 'SDK:Version'" + echo " --keep-zip,-KeepZip If set, downloaded file is kept." + echo " --zip-path, -ZipPath If set, downloaded file is stored at the specified path." + echo " -?,--?,-h,--help,-Help Shows this help message" + echo "" + echo "Install Location:" + echo " Location is chosen in following order:" + echo " - --install-dir option" + echo " - Environmental variable DOTNET_INSTALL_DIR" + echo " - $HOME/.dotnet" + exit 0 + ;; + *) + say_err "Unknown argument \`$name\`" + exit 1 + ;; + esac + + shift +done + +say_verbose "Note that the intended use of this script is for Continuous Integration (CI) scenarios, where:" +say_verbose "- The SDK needs to be installed without user interaction and without admin rights." +say_verbose "- The SDK installation doesn't need to persist across multiple CI runs." +say_verbose "To set up a development environment or to run apps, use installers rather than this script. Visit https://dotnet.microsoft.com/download to get the installer.\n" + +if [ "$internal" = true ] && [ -z "$(echo $feed_credential)" ]; then + message="Provide credentials via --feed-credential parameter." + if [ "$dry_run" = true ]; then + say_warning "$message" + else + say_err "$message" + exit 1 + fi +fi + +check_min_reqs +calculate_vars +# generate_regular_links call below will 'exit' if the determined version is already installed. +generate_download_links + +if [[ "$dry_run" = true ]]; then + print_dry_run + exit 0 +fi + +install_dotnet + +bin_path="$(get_absolute_path "$(combine_paths "$install_root" "$bin_folder_relative_path")")" +if [ "$no_path" = false ]; then + say "Adding to current process PATH: \`$bin_path\`. Note: This change will be visible only when sourcing script." + export PATH="$bin_path":"$PATH" +else + say "Binaries of dotnet can be found in $bin_path" +fi + +say "Note that the script does not resolve dependencies during installation." +say "To check the list of dependencies, go to https://learn.microsoft.com/dotnet/core/install, select your operating system and check the \"Dependencies\" section." +say "Installation finished successfully." From 23611d25ba3ab2c135946b34e5774b24857ecb96 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 22 Aug 2025 20:47:17 +0000 Subject: [PATCH 04/10] Clean up copilot-instructions.md and remove unnecessary files Co-authored-by: KSemenenko <4385716+KSemenenko@users.noreply.github.com> --- .github/copilot-instructions.md | 234 +- .../ManagedCode.Communication.Tests.trx | 381 ---- dotnet-install.sh | 1888 ----------------- dotnet-install.sh.1 | 1888 ----------------- 4 files changed, 37 insertions(+), 4354 deletions(-) delete mode 100644 ManagedCode.Communication.Tests/ManagedCode.Communication.Tests.trx delete mode 100755 dotnet-install.sh delete mode 100644 dotnet-install.sh.1 diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index bcfe580..88f9311 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -2,224 +2,64 @@ A high-performance .NET 9.0 library providing Result types and railway-oriented programming patterns for robust error handling in distributed applications. -**ALWAYS follow these instructions first and only fall back to search or bash commands when you encounter unexpected information that does not match the instructions below.** - -## Working Effectively - -### Bootstrap and Build the Repository - -**NEVER CANCEL builds or tests** - they complete quickly but timeouts should be generous. - -1. **Install .NET 9.0 SDK (REQUIRED):** - ```bash - wget -q https://dot.net/v1/dotnet-install.sh && chmod +x dotnet-install.sh - ./dotnet-install.sh --version 9.0.100 --install-dir ~/.dotnet - export PATH="/home/runner/.dotnet:$PATH" - ``` - - Verification: `dotnet --version` should show `9.0.100` - - **CRITICAL**: The project targets .NET 9.0 and will NOT build with older SDKs - -2. **Restore packages (takes ~7 seconds):** - ```bash - dotnet restore ManagedCode.Communication/ManagedCode.Communication.csproj - dotnet restore ManagedCode.Communication.AspNetCore/ManagedCode.Communication.AspNetCore.csproj - dotnet restore ManagedCode.Communication.Orleans/ManagedCode.Communication.Orleans.csproj - dotnet restore ManagedCode.Communication.Tests/ManagedCode.Communication.Tests.csproj - dotnet restore ManagedCode.Communication.Benchmark/ManagedCode.Communication.Benchmark.csproj - ``` - - **NEVER CANCEL**: Set timeout to 300+ seconds. Build may take up to 5 minutes on slow connections. - -3. **Build all projects (takes ~12 seconds):** - ```bash - dotnet build ManagedCode.Communication/ManagedCode.Communication.csproj --configuration Release - dotnet build ManagedCode.Communication.AspNetCore/ManagedCode.Communication.AspNetCore.csproj --configuration Release - dotnet build ManagedCode.Communication.Orleans/ManagedCode.Communication.Orleans.csproj --configuration Release - dotnet build ManagedCode.Communication.Tests/ManagedCode.Communication.Tests.csproj --configuration Release - dotnet build ManagedCode.Communication.Benchmark/ManagedCode.Communication.Benchmark.csproj --configuration Release - ``` - - **NEVER CANCEL**: Set timeout to 600+ seconds. Full build may take up to 10 minutes. - -### Testing - -**Run all tests (takes ~5 seconds, 638 tests):** -```bash -dotnet test ManagedCode.Communication.Tests/ManagedCode.Communication.Tests.csproj --configuration Release --no-build --verbosity normal -``` -- **NEVER CANCEL**: Set timeout to 300+ seconds. Tests may take up to 5 minutes in CI environments. -- **Expected result**: All 638 tests pass with 70%+ code coverage -- Tests include ASP.NET Core integration tests with real HTTP requests and SignalR hubs +## Project Requirements -### Code Quality and Formatting +This project requires **.NET 9.0 SDK**. Install it using: -**Format code (IMPORTANT - there are known line ending issues):** ```bash -# Format individual projects (the repository has CRLF line ending issues on Linux) -dotnet format ManagedCode.Communication/ManagedCode.Communication.csproj --verbosity minimal -dotnet format ManagedCode.Communication.AspNetCore/ManagedCode.Communication.AspNetCore.csproj --verbosity minimal -dotnet format ManagedCode.Communication.Orleans/ManagedCode.Communication.Orleans.csproj --verbosity minimal -dotnet format ManagedCode.Communication.Tests/ManagedCode.Communication.Tests.csproj --verbosity minimal +dotnet --list-sdks # Check if 9.0.x is available ``` -- **NOTE**: The repository uses CRLF line endings (.editorconfig sets `end_of_line = crlf`) which may cause formatting errors on Linux. This is expected behavior. -- **DO NOT** try to fix line ending issues - they are intentional for Windows compatibility - -## Validation Scenarios - -**ALWAYS test actual functionality after making changes** by running through these complete scenarios: - -### 1. Basic Library Functionality Test -Create a test console app to verify core Result functionality: +If not available, install .NET 9.0: ```bash -cd /tmp -dotnet new console -n TestLibrary -cd TestLibrary -dotnet add reference /home/runner/work/Communication/Communication/ManagedCode.Communication/ManagedCode.Communication.csproj +wget -q https://dot.net/v1/dotnet-install.sh && chmod +x dotnet-install.sh +./dotnet-install.sh --version 9.0.100 --install-dir ~/.dotnet +export PATH="~/.dotnet:$PATH" ``` -Test Program.cs content: -```csharp -using ManagedCode.Communication; -using ManagedCode.Communication.Extensions; -using System; - -Console.WriteLine("Testing ManagedCode.Communication Library"); - -// Test basic Result creation -var successResult = Result.Succeed(); -var failureResult = Result.Fail("Something went wrong"); -Console.WriteLine($"Success: {successResult.IsSuccess}, Failure: {failureResult.IsSuccess}"); +## Build and Test -// Test Result with values -var userResult = Result.Succeed("John Doe"); -var notFoundResult = Result.FailNotFound("User not found"); -Console.WriteLine($"User: {userResult.Value}, NotFound: {notFoundResult.Problem?.Title}"); - -// Test railway-oriented programming chain -var chainResult = ValidateEmail("test@example.com") - .Then(email => Result.Succeed(email.ToLower())) - .Then(email => Result.Succeed($"Processed: {email}")); -Console.WriteLine($"Chain result: {chainResult.IsSuccess}, Value: {chainResult.Value}"); - -static Result ValidateEmail(string email) => - email.Contains("@") ? Result.Succeed(email) : Result.FailValidation(("email", "Invalid format")); +**Restore packages:** +```bash +dotnet restore ``` -Run: `dotnet run` -**Expected output**: Success messages showing Result types work correctly with railway-oriented programming. - -### 2. ASP.NET Core Integration Test -The test suite includes real ASP.NET Core integration tests that: -- Start an actual web server on localhost -- Test Result to HTTP status code mapping (200, 400, 403, 404, 500) -- Test SignalR hub integration with Result types -- Test authentication and authorization flows - -Run the integration tests to verify web functionality: +**Build all projects:** ```bash -dotnet test ManagedCode.Communication.Tests/ManagedCode.Communication.Tests.csproj --filter "AspNetCore" --verbosity normal +dotnet build --configuration Release ``` -### 3. Performance Benchmark Test -Test the benchmark suite (interactive): +**Run tests:** ```bash -dotnet run -c Release --project ManagedCode.Communication.Benchmark/ManagedCode.Communication.Benchmark.csproj -- --job dry -# When prompted, enter "*" to run all benchmarks or "0" for specific benchmark +dotnet test --configuration Release --no-build ``` -**Expected**: Benchmark starts and shows performance comparisons between Result types and alternatives. - -## Key Projects in the Codebase - -### Core Projects -- **ManagedCode.Communication** - Core Result types, railway-oriented programming extensions, command pattern -- **ManagedCode.Communication.AspNetCore** - ASP.NET Core filters, middleware, and Result-to-HTTP mapping -- **ManagedCode.Communication.Orleans** - Microsoft Orleans serialization support for distributed Result types -- **ManagedCode.Communication.Tests** - Comprehensive test suite (638 tests) -- **ManagedCode.Communication.Benchmark** - BenchmarkDotNet performance testing -### Key Source Locations -- **Result Types**: `ManagedCode.Communication/Result/`, `ManagedCode.Communication/ResultT/` -- **Railway Extensions**: `ManagedCode.Communication/Extensions/RailwayExtensions*.cs` -- **Command Pattern**: `ManagedCode.Communication/Commands/` -- **ASP.NET Core Filters**: `ManagedCode.Communication.AspNetCore/Filters/` -- **Orleans Serializers**: `ManagedCode.Communication.Orleans/Serializers/` +## Project Structure -## Build Timing Expectations +- **ManagedCode.Communication** - Core Result types and railway-oriented programming extensions +- **ManagedCode.Communication.AspNetCore** - ASP.NET Core integration, filters, and middleware +- **ManagedCode.Communication.Orleans** - Microsoft Orleans serialization support +- **ManagedCode.Communication.Tests** - Test suite +- **ManagedCode.Communication.Benchmark** - Performance benchmarks -**All times measured on standard GitHub Actions runners:** +## Key Concepts -| Operation | Expected Time | Timeout Setting | -|-----------|---------------|-----------------| -| .NET 9 SDK Install | 30-60 seconds | 300 seconds | -| Package Restore | 5-10 seconds | 300 seconds | -| Full Build | 10-15 seconds | 600 seconds | -| Test Execution | 4-6 seconds | 300 seconds | -| Format Check | 3-8 seconds | 300 seconds | -| Single Project Build | 1-4 seconds | 300 seconds | - -**CRITICAL: NEVER CANCEL** any of these operations. Always wait for completion. - -## Common Development Tasks - -### Adding New Result Types -- Extend base classes in `ManagedCode.Communication/Result/` or `ManagedCode.Communication/ResultT/` -- Add corresponding tests in `ManagedCode.Communication.Tests/` -- Update ASP.NET Core mappings in `ManagedCode.Communication.AspNetCore/Extensions/ResultExtensions.cs` - -### Adding ASP.NET Core Features -- Create filters in `ManagedCode.Communication.AspNetCore/Filters/` -- Add extension methods in `ManagedCode.Communication.AspNetCore/Extensions/` -- Test with real HTTP scenarios in test project's TestApp - -### Performance Testing -- Add benchmarks to `ManagedCode.Communication.Benchmark/` -- Use BenchmarkDotNet attributes for proper measurement -- Always compare against existing baseline implementations - -## Troubleshooting - -### Build Issues -- **".NET 9.0 not found"**: Install .NET 9.0 SDK using the exact commands above -- **"Project not found"**: Use individual project files, not solution file (`.slnx` format not fully supported) -- **"Format errors"**: Line ending issues are expected on Linux due to CRLF settings - -### Test Issues -- **Tests timeout**: ASP.NET Core integration tests start real servers and may take longer in CI environments -- **Orleans tests fail**: Ensure all projects are built before running tests (Orleans needs compiled assemblies) - -### Expected CI Workflow -The `.github/workflows/ci.yml` runs: -1. .NET 9.0 setup -2. `dotnet restore` -3. `dotnet build --configuration Release --no-restore` -4. `dotnet test --configuration Release --no-build` -5. Code coverage upload - -**Always run the same sequence locally** to ensure CI compatibility. - -## Library Usage Examples - -The library provides Result types for error handling without exceptions: +This library implements railway-oriented programming patterns using Result types for error handling without exceptions: ```csharp -// Basic usage -Result user = await GetUserAsync(id); -if (user.IsSuccess) - Console.WriteLine($"Found: {user.Value.Name}"); -else - Console.WriteLine($"Error: {user.Problem.Title}"); +// Basic Result usage +var success = Result.Succeed(); +var failure = Result.Fail("Error message"); -// Railway-oriented programming -var result = await ValidateUserAsync(userData) - .ThenAsync(user => SaveUserAsync(user)) - .ThenAsync(user => SendWelcomeEmailAsync(user)) - .ThenAsync(user => LogUserCreationAsync(user)); - -// ASP.NET Core integration (automatic HTTP status mapping) -[HttpPost] -public Result CreateUser(CreateUserRequest request) => - ValidateRequest(request) - .Then(CreateUserFromRequest) - .Then(SaveUserToDatabase); -``` +// Generic Result with value +var result = Result.Succeed("Hello World"); +if (result.IsSuccess) +{ + Console.WriteLine(result.Value); +} -Always test these patterns when making changes to ensure the library's core value proposition remains intact. \ No newline at end of file +// Railway-oriented programming +var chainResult = ValidateEmail("test@example.com") + .Then(email => ProcessEmail(email)) + .Then(processed => SaveToDatabase(processed)); +``` \ No newline at end of file diff --git a/ManagedCode.Communication.Tests/ManagedCode.Communication.Tests.trx b/ManagedCode.Communication.Tests/ManagedCode.Communication.Tests.trx deleted file mode 100644 index 443530b..0000000 --- a/ManagedCode.Communication.Tests/ManagedCode.Communication.Tests.trx +++ /dev/null @@ -1,381 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - [xUnit.net 00:00:00.00] xUnit.net VSTest Adapter v3.1.4+50e68bbb8b (64-bit .NET 9.0.0) -[xUnit.net 00:00:00.10] Discovering: ManagedCode.Communication.Tests -[xUnit.net 00:00:00.20] Discovered: ManagedCode.Communication.Tests -[xUnit.net 00:00:00.23] Starting: ManagedCode.Communication.Tests -info: Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager[62] - User profile is available. Using '/home/runner/.aspnet/DataProtection-Keys' as key repository; keys will not be encrypted at rest. -info: Microsoft.AspNetCore.Hosting.Diagnostics[1] - Request starting HTTP/1.1 GET http://localhost/test/collection-success - - - -info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0] - Executing endpoint 'ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.GetCollectionSuccess (ManagedCode.Communication.Tests)' -info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[102] - Route matched with {action = "GetCollectionSuccess", controller = "Test"}. Executing controller action with signature ManagedCode.Communication.CollectionResultT.CollectionResult`1[ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestModel] GetCollectionSuccess() on controller ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController (ManagedCode.Communication.Tests). -info: Microsoft.AspNetCore.Mvc.Infrastructure.ObjectResultExecutor[1] - Executing ObjectResult, writing value of type 'ManagedCode.Communication.CollectionResultT.CollectionResult`1[[ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestModel, ManagedCode.Communication.Tests, Version=9.6.2.0, Culture=neutral, PublicKeyToken=null]]'. -info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[105] - Executed action ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.GetCollectionSuccess (ManagedCode.Communication.Tests) in 55.0109ms -info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1] - Executed endpoint 'ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.GetCollectionSuccess (ManagedCode.Communication.Tests)' -info: Microsoft.AspNetCore.Hosting.Diagnostics[2] - Request finished HTTP/1.1 GET http://localhost/test/collection-success - 200 - application/json;+charset=utf-8 98.4968ms -info: Microsoft.AspNetCore.Hosting.Diagnostics[1] - Request starting HTTP/1.1 POST http://localhost/test/validate - application/json;+charset=utf-8 38 -info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0] - Executing endpoint 'ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.Validate (ManagedCode.Communication.Tests)' -info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[102] - Route matched with {action = "Validate", controller = "Test"}. Executing controller action with signature Microsoft.AspNetCore.Mvc.ActionResult`1[System.String] Validate(ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestValidationModel) on controller ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController (ManagedCode.Communication.Tests). -warn: ManagedCode.Communication.AspNetCore.Filters.CommunicationModelValidationFilter[0] - Model validation failed for ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.Validate (ManagedCode.Communication.Tests) -info: Microsoft.AspNetCore.Mvc.Infrastructure.ObjectResultExecutor[1] - Executing BadRequestObjectResult, writing value of type 'ManagedCode.Communication.Result'. -info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[105] - Executed action ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.Validate (ManagedCode.Communication.Tests) in 46.9148ms -info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1] - Executed endpoint 'ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.Validate (ManagedCode.Communication.Tests)' -info: Microsoft.AspNetCore.Hosting.Diagnostics[2] - Request finished HTTP/1.1 POST http://localhost/test/validate - 400 - application/json;+charset=utf-8 56.9092ms -info: Microsoft.AspNetCore.Hosting.Diagnostics[1] - Request starting HTTP/1.1 GET http://localhost/test/custom-problem - - - -info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0] - Executing endpoint 'ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.CustomProblem (ManagedCode.Communication.Tests)' -info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[102] - Route matched with {action = "CustomProblem", controller = "Test"}. Executing controller action with signature Microsoft.AspNetCore.Mvc.ActionResult CustomProblem() on controller ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController (ManagedCode.Communication.Tests). -info: Microsoft.AspNetCore.Mvc.Infrastructure.ObjectResultExecutor[1] - Executing ObjectResult, writing value of type 'ManagedCode.Communication.Problem'. -info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[105] - Executed action ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.CustomProblem (ManagedCode.Communication.Tests) in 2.2734ms -info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1] - Executed endpoint 'ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.CustomProblem (ManagedCode.Communication.Tests)' -info: Microsoft.AspNetCore.Hosting.Diagnostics[2] - Request finished HTTP/1.1 GET http://localhost/test/custom-problem - 409 - application/json;+charset=utf-8 4.3435ms -info: Microsoft.AspNetCore.Hosting.Diagnostics[1] - Request starting HTTP/1.1 GET http://localhost/test/throw-exception - - - -info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0] - Executing endpoint 'ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.ThrowException (ManagedCode.Communication.Tests)' -info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[102] - Route matched with {action = "ThrowException", controller = "Test"}. Executing controller action with signature System.String ThrowException() on controller ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController (ManagedCode.Communication.Tests). -fail: ManagedCode.Communication.AspNetCore.Filters.CommunicationExceptionFilter[0] - Unhandled exception in Test.ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.ThrowException (ManagedCode.Communication.Tests) - System.InvalidOperationException: This is a test exception for integration testing - at ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.ThrowException() in /_/ManagedCode.Communication.Tests/Common/TestApp/Controllers/TestController.cs:line 130 - at lambda_method81(Closure, Object, Object[]) - at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.SyncObjectResultExecutor.Execute(ActionContext actionContext, IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments) - at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeActionMethodAsync() - at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted) - at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeNextActionFilterAsync() - --- End of stack trace from previous location --- - at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context) - at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted) - at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeInnerFilterAsync() - --- End of stack trace from previous location --- - at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextExceptionFilterAsync>g__Awaited|26_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted) -info: ManagedCode.Communication.AspNetCore.Filters.CommunicationExceptionFilter[0] - Exception handled by CommunicationExceptionFilter for Test.ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.ThrowException (ManagedCode.Communication.Tests) -info: Microsoft.AspNetCore.Mvc.Infrastructure.ObjectResultExecutor[1] - Executing ObjectResult, writing value of type 'ManagedCode.Communication.Result'. -info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[105] - Executed action ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.ThrowException (ManagedCode.Communication.Tests) in 8.6866ms -info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1] - Executed endpoint 'ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.ThrowException (ManagedCode.Communication.Tests)' -info: Microsoft.AspNetCore.Hosting.Diagnostics[2] - Request finished HTTP/1.1 GET http://localhost/test/throw-exception - 400 - application/json;+charset=utf-8 9.4554ms -info: Microsoft.AspNetCore.Hosting.Diagnostics[1] - Request starting HTTP/1.1 GET http://localhost/test/collection-empty - - - -info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0] - Executing endpoint 'ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.GetCollectionEmpty (ManagedCode.Communication.Tests)' -info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[102] - Route matched with {action = "GetCollectionEmpty", controller = "Test"}. Executing controller action with signature ManagedCode.Communication.CollectionResultT.CollectionResult`1[ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestModel] GetCollectionEmpty() on controller ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController (ManagedCode.Communication.Tests). -info: Microsoft.AspNetCore.Mvc.Infrastructure.ObjectResultExecutor[1] - Executing ObjectResult, writing value of type 'ManagedCode.Communication.CollectionResultT.CollectionResult`1[[ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestModel, ManagedCode.Communication.Tests, Version=9.6.2.0, Culture=neutral, PublicKeyToken=null]]'. -info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[105] - Executed action ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.GetCollectionEmpty (ManagedCode.Communication.Tests) in 0.6124ms -info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1] - Executed endpoint 'ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.GetCollectionEmpty (ManagedCode.Communication.Tests)' -info: Microsoft.AspNetCore.Hosting.Diagnostics[2] - Request finished HTTP/1.1 GET http://localhost/test/collection-empty - 200 - application/json;+charset=utf-8 2.9464ms -info: Microsoft.AspNetCore.Hosting.Diagnostics[1] - Request starting HTTP/1.1 POST http://localhost/test/validate - application/json;+charset=utf-8 55 -info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0] - Executing endpoint 'ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.Validate (ManagedCode.Communication.Tests)' -info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[102] - Route matched with {action = "Validate", controller = "Test"}. Executing controller action with signature Microsoft.AspNetCore.Mvc.ActionResult`1[System.String] Validate(ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestValidationModel) on controller ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController (ManagedCode.Communication.Tests). -info: Microsoft.AspNetCore.Mvc.Infrastructure.ObjectResultExecutor[1] - Executing OkObjectResult, writing value of type 'System.String'. -info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[105] - Executed action ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.Validate (ManagedCode.Communication.Tests) in 2.0563ms -info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1] - Executed endpoint 'ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.Validate (ManagedCode.Communication.Tests)' -info: Microsoft.AspNetCore.Hosting.Diagnostics[2] - Request finished HTTP/1.1 POST http://localhost/test/validate - 200 - text/plain;+charset=utf-8 2.3331ms -info: Microsoft.AspNetCore.Hosting.Diagnostics[1] - Request starting HTTP/1.1 GET http://localhost/test/result-notfound - - - -info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0] - Executing endpoint 'ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.GetResultNotFoundTest (ManagedCode.Communication.Tests)' -info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[102] - Route matched with {action = "GetResultNotFoundTest", controller = "Test"}. Executing controller action with signature ManagedCode.Communication.Result`1[System.String] GetResultNotFoundTest() on controller ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController (ManagedCode.Communication.Tests). -info: Microsoft.AspNetCore.Mvc.Infrastructure.ObjectResultExecutor[1] - Executing ObjectResult, writing value of type 'ManagedCode.Communication.Result`1[[System.String, System.Private.CoreLib, Version=9.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]]'. -info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[105] - Executed action ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.GetResultNotFoundTest (ManagedCode.Communication.Tests) in 6.5134ms -info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1] - Executed endpoint 'ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.GetResultNotFoundTest (ManagedCode.Communication.Tests)' -info: Microsoft.AspNetCore.Hosting.Diagnostics[2] - Request finished HTTP/1.1 GET http://localhost/test/result-notfound - 404 - application/json;+charset=utf-8 7.2504ms -info: Microsoft.AspNetCore.Hosting.Diagnostics[1] - Request starting HTTP/1.1 GET http://localhost/test/result-failure - - - -info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0] - Executing endpoint 'ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.GetResultFailure (ManagedCode.Communication.Tests)' -info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[102] - Route matched with {action = "GetResultFailure", controller = "Test"}. Executing controller action with signature ManagedCode.Communication.Result`1[System.String] GetResultFailure() on controller ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController (ManagedCode.Communication.Tests). -info: Microsoft.AspNetCore.Mvc.Infrastructure.ObjectResultExecutor[1] - Executing ObjectResult, writing value of type 'ManagedCode.Communication.Result`1[[System.String, System.Private.CoreLib, Version=9.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]]'. -info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[105] - Executed action ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.GetResultFailure (ManagedCode.Communication.Tests) in 0.5116ms -info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1] - Executed endpoint 'ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.GetResultFailure (ManagedCode.Communication.Tests)' -info: Microsoft.AspNetCore.Hosting.Diagnostics[2] - Request finished HTTP/1.1 GET http://localhost/test/result-failure - 400 - application/json;+charset=utf-8 1.2435ms -info: Microsoft.AspNetCore.Hosting.Diagnostics[1] - Request starting HTTP/1.1 GET http://localhost/test/result-success - - - -info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0] - Executing endpoint 'ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.GetResultSuccessWithValue (ManagedCode.Communication.Tests)' -info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[102] - Route matched with {action = "GetResultSuccessWithValue", controller = "Test"}. Executing controller action with signature ManagedCode.Communication.Result`1[ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestModel] GetResultSuccessWithValue() on controller ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController (ManagedCode.Communication.Tests). -info: Microsoft.AspNetCore.Mvc.Infrastructure.ObjectResultExecutor[1] - Executing ObjectResult, writing value of type 'ManagedCode.Communication.Result`1[[ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestModel, ManagedCode.Communication.Tests, Version=9.6.2.0, Culture=neutral, PublicKeyToken=null]]'. -info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[105] - Executed action ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.GetResultSuccessWithValue (ManagedCode.Communication.Tests) in 1.7778ms -info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1] - Executed endpoint 'ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.GetResultSuccessWithValue (ManagedCode.Communication.Tests)' -info: Microsoft.AspNetCore.Hosting.Diagnostics[2] - Request finished HTTP/1.1 GET http://localhost/test/result-success - 200 - application/json;+charset=utf-8 2.6297ms -info: Microsoft.AspNetCore.Hosting.Diagnostics[1] - Request starting HTTP/1.1 GET http://localhost/test/enum-error - - - -info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0] - Executing endpoint 'ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.GetEnumError (ManagedCode.Communication.Tests)' -info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[102] - Route matched with {action = "GetEnumError", controller = "Test"}. Executing controller action with signature ManagedCode.Communication.Result`1[System.String] GetEnumError() on controller ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController (ManagedCode.Communication.Tests). -info: Microsoft.AspNetCore.Mvc.Infrastructure.ObjectResultExecutor[1] - Executing ObjectResult, writing value of type 'ManagedCode.Communication.Result`1[[System.String, System.Private.CoreLib, Version=9.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]]'. -info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[105] - Executed action ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.GetEnumError (ManagedCode.Communication.Tests) in 0.8662ms -info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1] - Executed endpoint 'ManagedCode.Communication.Tests.Common.TestApp.Controllers.TestController.GetEnumError (ManagedCode.Communication.Tests)' -info: Microsoft.AspNetCore.Hosting.Diagnostics[2] - Request finished HTTP/1.1 GET http://localhost/test/enum-error - 400 - application/json;+charset=utf-8 1.6213ms -[xUnit.net 00:00:02.02] Finished: ManagedCode.Communication.Tests - - - - \ No newline at end of file diff --git a/dotnet-install.sh b/dotnet-install.sh deleted file mode 100755 index 034d2df..0000000 --- a/dotnet-install.sh +++ /dev/null @@ -1,1888 +0,0 @@ -#!/usr/bin/env bash -# Copyright (c) .NET Foundation and contributors. All rights reserved. -# Licensed under the MIT license. See LICENSE file in the project root for full license information. -# - -# Stop script on NZEC -set -e -# Stop script if unbound variable found (use ${var:-} if intentional) -set -u -# By default cmd1 | cmd2 returns exit code of cmd2 regardless of cmd1 success -# This is causing it to fail -set -o pipefail - -# Use in the the functions: eval $invocation -invocation='say_verbose "Calling: ${yellow:-}${FUNCNAME[0]} ${green:-}$*${normal:-}"' - -# standard output may be used as a return value in the functions -# we need a way to write text on the screen in the functions so that -# it won't interfere with the return value. -# Exposing stream 3 as a pipe to standard output of the script itself -exec 3>&1 - -# Setup some colors to use. These need to work in fairly limited shells, like the Ubuntu Docker container where there are only 8 colors. -# See if stdout is a terminal -if [ -t 1 ] && command -v tput > /dev/null; then - # see if it supports colors - ncolors=$(tput colors || echo 0) - if [ -n "$ncolors" ] && [ $ncolors -ge 8 ]; then - bold="$(tput bold || echo)" - normal="$(tput sgr0 || echo)" - black="$(tput setaf 0 || echo)" - red="$(tput setaf 1 || echo)" - green="$(tput setaf 2 || echo)" - yellow="$(tput setaf 3 || echo)" - blue="$(tput setaf 4 || echo)" - magenta="$(tput setaf 5 || echo)" - cyan="$(tput setaf 6 || echo)" - white="$(tput setaf 7 || echo)" - fi -fi - -say_warning() { - printf "%b\n" "${yellow:-}dotnet_install: Warning: $1${normal:-}" >&3 -} - -say_err() { - printf "%b\n" "${red:-}dotnet_install: Error: $1${normal:-}" >&2 -} - -say() { - # using stream 3 (defined in the beginning) to not interfere with stdout of functions - # which may be used as return value - printf "%b\n" "${cyan:-}dotnet-install:${normal:-} $1" >&3 -} - -say_verbose() { - if [ "$verbose" = true ]; then - say "$1" - fi -} - -# This platform list is finite - if the SDK/Runtime has supported Linux distribution-specific assets, -# then and only then should the Linux distribution appear in this list. -# Adding a Linux distribution to this list does not imply distribution-specific support. -get_legacy_os_name_from_platform() { - eval $invocation - - platform="$1" - case "$platform" in - "centos.7") - echo "centos" - return 0 - ;; - "debian.8") - echo "debian" - return 0 - ;; - "debian.9") - echo "debian.9" - return 0 - ;; - "fedora.23") - echo "fedora.23" - return 0 - ;; - "fedora.24") - echo "fedora.24" - return 0 - ;; - "fedora.27") - echo "fedora.27" - return 0 - ;; - "fedora.28") - echo "fedora.28" - return 0 - ;; - "opensuse.13.2") - echo "opensuse.13.2" - return 0 - ;; - "opensuse.42.1") - echo "opensuse.42.1" - return 0 - ;; - "opensuse.42.3") - echo "opensuse.42.3" - return 0 - ;; - "rhel.7"*) - echo "rhel" - return 0 - ;; - "ubuntu.14.04") - echo "ubuntu" - return 0 - ;; - "ubuntu.16.04") - echo "ubuntu.16.04" - return 0 - ;; - "ubuntu.16.10") - echo "ubuntu.16.10" - return 0 - ;; - "ubuntu.18.04") - echo "ubuntu.18.04" - return 0 - ;; - "alpine.3.4.3") - echo "alpine" - return 0 - ;; - esac - return 1 -} - -get_legacy_os_name() { - eval $invocation - - local uname=$(uname) - if [ "$uname" = "Darwin" ]; then - echo "osx" - return 0 - elif [ -n "$runtime_id" ]; then - echo $(get_legacy_os_name_from_platform "${runtime_id%-*}" || echo "${runtime_id%-*}") - return 0 - else - if [ -e /etc/os-release ]; then - . /etc/os-release - os=$(get_legacy_os_name_from_platform "$ID${VERSION_ID:+.${VERSION_ID}}" || echo "") - if [ -n "$os" ]; then - echo "$os" - return 0 - fi - fi - fi - - say_verbose "Distribution specific OS name and version could not be detected: UName = $uname" - return 1 -} - -get_linux_platform_name() { - eval $invocation - - if [ -n "$runtime_id" ]; then - echo "${runtime_id%-*}" - return 0 - else - if [ -e /etc/os-release ]; then - . /etc/os-release - echo "$ID${VERSION_ID:+.${VERSION_ID}}" - return 0 - elif [ -e /etc/redhat-release ]; then - local redhatRelease=$(&1 || true) | grep -q musl -} - -get_current_os_name() { - eval $invocation - - local uname=$(uname) - if [ "$uname" = "Darwin" ]; then - echo "osx" - return 0 - elif [ "$uname" = "FreeBSD" ]; then - echo "freebsd" - return 0 - elif [ "$uname" = "Linux" ]; then - local linux_platform_name="" - linux_platform_name="$(get_linux_platform_name)" || true - - if [ "$linux_platform_name" = "rhel.6" ]; then - echo $linux_platform_name - return 0 - elif is_musl_based_distro; then - echo "linux-musl" - return 0 - elif [ "$linux_platform_name" = "linux-musl" ]; then - echo "linux-musl" - return 0 - else - echo "linux" - return 0 - fi - fi - - say_err "OS name could not be detected: UName = $uname" - return 1 -} - -machine_has() { - eval $invocation - - command -v "$1" > /dev/null 2>&1 - return $? -} - -check_min_reqs() { - local hasMinimum=false - if machine_has "curl"; then - hasMinimum=true - elif machine_has "wget"; then - hasMinimum=true - fi - - if [ "$hasMinimum" = "false" ]; then - say_err "curl (recommended) or wget are required to download dotnet. Install missing prerequisite to proceed." - return 1 - fi - return 0 -} - -# args: -# input - $1 -to_lowercase() { - #eval $invocation - - echo "$1" | tr '[:upper:]' '[:lower:]' - return 0 -} - -# args: -# input - $1 -remove_trailing_slash() { - #eval $invocation - - local input="${1:-}" - echo "${input%/}" - return 0 -} - -# args: -# input - $1 -remove_beginning_slash() { - #eval $invocation - - local input="${1:-}" - echo "${input#/}" - return 0 -} - -# args: -# root_path - $1 -# child_path - $2 - this parameter can be empty -combine_paths() { - eval $invocation - - # TODO: Consider making it work with any number of paths. For now: - if [ ! -z "${3:-}" ]; then - say_err "combine_paths: Function takes two parameters." - return 1 - fi - - local root_path="$(remove_trailing_slash "$1")" - local child_path="$(remove_beginning_slash "${2:-}")" - say_verbose "combine_paths: root_path=$root_path" - say_verbose "combine_paths: child_path=$child_path" - echo "$root_path/$child_path" - return 0 -} - -get_machine_architecture() { - eval $invocation - - if command -v uname > /dev/null; then - CPUName=$(uname -m) - case $CPUName in - armv1*|armv2*|armv3*|armv4*|armv5*|armv6*) - echo "armv6-or-below" - return 0 - ;; - armv*l) - echo "arm" - return 0 - ;; - aarch64|arm64) - if [ "$(getconf LONG_BIT)" -lt 64 ]; then - # This is 32-bit OS running on 64-bit CPU (for example Raspberry Pi OS) - echo "arm" - return 0 - fi - echo "arm64" - return 0 - ;; - s390x) - echo "s390x" - return 0 - ;; - ppc64le) - echo "ppc64le" - return 0 - ;; - loongarch64) - echo "loongarch64" - return 0 - ;; - riscv64) - echo "riscv64" - return 0 - ;; - powerpc|ppc) - echo "ppc" - return 0 - ;; - esac - fi - - # Always default to 'x64' - echo "x64" - return 0 -} - -# args: -# architecture - $1 -get_normalized_architecture_from_architecture() { - eval $invocation - - local architecture="$(to_lowercase "$1")" - - if [[ $architecture == \ ]]; then - machine_architecture="$(get_machine_architecture)" - if [[ "$machine_architecture" == "armv6-or-below" ]]; then - say_err "Architecture \`$machine_architecture\` not supported. If you think this is a bug, report it at https://github.com/dotnet/install-scripts/issues" - return 1 - fi - - echo $machine_architecture - return 0 - fi - - case "$architecture" in - amd64|x64) - echo "x64" - return 0 - ;; - arm) - echo "arm" - return 0 - ;; - arm64) - echo "arm64" - return 0 - ;; - s390x) - echo "s390x" - return 0 - ;; - ppc64le) - echo "ppc64le" - return 0 - ;; - loongarch64) - echo "loongarch64" - return 0 - ;; - esac - - say_err "Architecture \`$architecture\` not supported. If you think this is a bug, report it at https://github.com/dotnet/install-scripts/issues" - return 1 -} - -# args: -# version - $1 -# channel - $2 -# architecture - $3 -get_normalized_architecture_for_specific_sdk_version() { - eval $invocation - - local is_version_support_arm64="$(is_arm64_supported "$1")" - local is_channel_support_arm64="$(is_arm64_supported "$2")" - local architecture="$3"; - local osname="$(get_current_os_name)" - - if [ "$osname" == "osx" ] && [ "$architecture" == "arm64" ] && { [ "$is_version_support_arm64" = false ] || [ "$is_channel_support_arm64" = false ]; }; then - #check if rosetta is installed - if [ "$(/usr/bin/pgrep oahd >/dev/null 2>&1;echo $?)" -eq 0 ]; then - say_verbose "Changing user architecture from '$architecture' to 'x64' because .NET SDKs prior to version 6.0 do not support arm64." - echo "x64" - return 0; - else - say_err "Architecture \`$architecture\` is not supported for .NET SDK version \`$version\`. Please install Rosetta to allow emulation of the \`$architecture\` .NET SDK on this platform" - return 1 - fi - fi - - echo "$architecture" - return 0 -} - -# args: -# version or channel - $1 -is_arm64_supported() { - # Extract the major version by splitting on the dot - major_version="${1%%.*}" - - # Check if the major version is a valid number and less than 6 - case "$major_version" in - [0-9]*) - if [ "$major_version" -lt 6 ]; then - echo false - return 0 - fi - ;; - esac - - echo true - return 0 -} - -# args: -# user_defined_os - $1 -get_normalized_os() { - eval $invocation - - local osname="$(to_lowercase "$1")" - if [ ! -z "$osname" ]; then - case "$osname" in - osx | freebsd | rhel.6 | linux-musl | linux) - echo "$osname" - return 0 - ;; - macos) - osname='osx' - echo "$osname" - return 0 - ;; - *) - say_err "'$user_defined_os' is not a supported value for --os option, supported values are: osx, macos, linux, linux-musl, freebsd, rhel.6. If you think this is a bug, report it at https://github.com/dotnet/install-scripts/issues." - return 1 - ;; - esac - else - osname="$(get_current_os_name)" || return 1 - fi - echo "$osname" - return 0 -} - -# args: -# quality - $1 -get_normalized_quality() { - eval $invocation - - local quality="$(to_lowercase "$1")" - if [ ! -z "$quality" ]; then - case "$quality" in - daily | preview) - echo "$quality" - return 0 - ;; - ga) - #ga quality is available without specifying quality, so normalizing it to empty - return 0 - ;; - *) - say_err "'$quality' is not a supported value for --quality option. Supported values are: daily, preview, ga. If you think this is a bug, report it at https://github.com/dotnet/install-scripts/issues." - return 1 - ;; - esac - fi - return 0 -} - -# args: -# channel - $1 -get_normalized_channel() { - eval $invocation - - local channel="$(to_lowercase "$1")" - - if [[ $channel == current ]]; then - say_warning 'Value "Current" is deprecated for -Channel option. Use "STS" instead.' - fi - - if [[ $channel == release/* ]]; then - say_warning 'Using branch name with -Channel option is no longer supported with newer releases. Use -Quality option with a channel in X.Y format instead.'; - fi - - if [ ! -z "$channel" ]; then - case "$channel" in - lts) - echo "LTS" - return 0 - ;; - sts) - echo "STS" - return 0 - ;; - current) - echo "STS" - return 0 - ;; - *) - echo "$channel" - return 0 - ;; - esac - fi - - return 0 -} - -# args: -# runtime - $1 -get_normalized_product() { - eval $invocation - - local product="" - local runtime="$(to_lowercase "$1")" - if [[ "$runtime" == "dotnet" ]]; then - product="dotnet-runtime" - elif [[ "$runtime" == "aspnetcore" ]]; then - product="aspnetcore-runtime" - elif [ -z "$runtime" ]; then - product="dotnet-sdk" - fi - echo "$product" - return 0 -} - -# The version text returned from the feeds is a 1-line or 2-line string: -# For the SDK and the dotnet runtime (2 lines): -# Line 1: # commit_hash -# Line 2: # 4-part version -# For the aspnetcore runtime (1 line): -# Line 1: # 4-part version - -# args: -# version_text - stdin -get_version_from_latestversion_file_content() { - eval $invocation - - cat | tail -n 1 | sed 's/\r$//' - return 0 -} - -# args: -# install_root - $1 -# relative_path_to_package - $2 -# specific_version - $3 -is_dotnet_package_installed() { - eval $invocation - - local install_root="$1" - local relative_path_to_package="$2" - local specific_version="${3//[$'\t\r\n']}" - - local dotnet_package_path="$(combine_paths "$(combine_paths "$install_root" "$relative_path_to_package")" "$specific_version")" - say_verbose "is_dotnet_package_installed: dotnet_package_path=$dotnet_package_path" - - if [ -d "$dotnet_package_path" ]; then - return 0 - else - return 1 - fi -} - -# args: -# downloaded file - $1 -# remote_file_size - $2 -validate_remote_local_file_sizes() -{ - eval $invocation - - local downloaded_file="$1" - local remote_file_size="$2" - local file_size='' - - if [[ "$OSTYPE" == "linux-gnu"* ]]; then - file_size="$(stat -c '%s' "$downloaded_file")" - elif [[ "$OSTYPE" == "darwin"* ]]; then - # hardcode in order to avoid conflicts with GNU stat - file_size="$(/usr/bin/stat -f '%z' "$downloaded_file")" - fi - - if [ -n "$file_size" ]; then - say "Downloaded file size is $file_size bytes." - - if [ -n "$remote_file_size" ] && [ -n "$file_size" ]; then - if [ "$remote_file_size" -ne "$file_size" ]; then - say "The remote and local file sizes are not equal. The remote file size is $remote_file_size bytes and the local size is $file_size bytes. The local package may be corrupted." - else - say "The remote and local file sizes are equal." - fi - fi - - else - say "Either downloaded or local package size can not be measured. One of them may be corrupted." - fi -} - -# args: -# azure_feed - $1 -# channel - $2 -# normalized_architecture - $3 -get_version_from_latestversion_file() { - eval $invocation - - local azure_feed="$1" - local channel="$2" - local normalized_architecture="$3" - - local version_file_url=null - if [[ "$runtime" == "dotnet" ]]; then - version_file_url="$azure_feed/Runtime/$channel/latest.version" - elif [[ "$runtime" == "aspnetcore" ]]; then - version_file_url="$azure_feed/aspnetcore/Runtime/$channel/latest.version" - elif [ -z "$runtime" ]; then - version_file_url="$azure_feed/Sdk/$channel/latest.version" - else - say_err "Invalid value for \$runtime" - return 1 - fi - say_verbose "get_version_from_latestversion_file: latest url: $version_file_url" - - download "$version_file_url" || return $? - return 0 -} - -# args: -# json_file - $1 -parse_globaljson_file_for_version() { - eval $invocation - - local json_file="$1" - if [ ! -f "$json_file" ]; then - say_err "Unable to find \`$json_file\`" - return 1 - fi - - sdk_section=$(cat $json_file | tr -d "\r" | awk '/"sdk"/,/}/') - if [ -z "$sdk_section" ]; then - say_err "Unable to parse the SDK node in \`$json_file\`" - return 1 - fi - - sdk_list=$(echo $sdk_section | awk -F"[{}]" '{print $2}') - sdk_list=${sdk_list//[\" ]/} - sdk_list=${sdk_list//,/$'\n'} - - local version_info="" - while read -r line; do - IFS=: - while read -r key value; do - if [[ "$key" == "version" ]]; then - version_info=$value - fi - done <<< "$line" - done <<< "$sdk_list" - if [ -z "$version_info" ]; then - say_err "Unable to find the SDK:version node in \`$json_file\`" - return 1 - fi - - unset IFS; - echo "$version_info" - return 0 -} - -# args: -# azure_feed - $1 -# channel - $2 -# normalized_architecture - $3 -# version - $4 -# json_file - $5 -get_specific_version_from_version() { - eval $invocation - - local azure_feed="$1" - local channel="$2" - local normalized_architecture="$3" - local version="$(to_lowercase "$4")" - local json_file="$5" - - if [ -z "$json_file" ]; then - if [[ "$version" == "latest" ]]; then - local version_info - version_info="$(get_version_from_latestversion_file "$azure_feed" "$channel" "$normalized_architecture" false)" || return 1 - say_verbose "get_specific_version_from_version: version_info=$version_info" - echo "$version_info" | get_version_from_latestversion_file_content - return 0 - else - echo "$version" - return 0 - fi - else - local version_info - version_info="$(parse_globaljson_file_for_version "$json_file")" || return 1 - echo "$version_info" - return 0 - fi -} - -# args: -# azure_feed - $1 -# channel - $2 -# normalized_architecture - $3 -# specific_version - $4 -# normalized_os - $5 -construct_download_link() { - eval $invocation - - local azure_feed="$1" - local channel="$2" - local normalized_architecture="$3" - local specific_version="${4//[$'\t\r\n']}" - local specific_product_version="$(get_specific_product_version "$1" "$4")" - local osname="$5" - - local download_link=null - if [[ "$runtime" == "dotnet" ]]; then - download_link="$azure_feed/Runtime/$specific_version/dotnet-runtime-$specific_product_version-$osname-$normalized_architecture.tar.gz" - elif [[ "$runtime" == "aspnetcore" ]]; then - download_link="$azure_feed/aspnetcore/Runtime/$specific_version/aspnetcore-runtime-$specific_product_version-$osname-$normalized_architecture.tar.gz" - elif [ -z "$runtime" ]; then - download_link="$azure_feed/Sdk/$specific_version/dotnet-sdk-$specific_product_version-$osname-$normalized_architecture.tar.gz" - else - return 1 - fi - - echo "$download_link" - return 0 -} - -# args: -# azure_feed - $1 -# specific_version - $2 -# download link - $3 (optional) -get_specific_product_version() { - # If we find a 'productVersion.txt' at the root of any folder, we'll use its contents - # to resolve the version of what's in the folder, superseding the specified version. - # if 'productVersion.txt' is missing but download link is already available, product version will be taken from download link - eval $invocation - - local azure_feed="$1" - local specific_version="${2//[$'\t\r\n']}" - local package_download_link="" - if [ $# -gt 2 ]; then - local package_download_link="$3" - fi - local specific_product_version=null - - # Try to get the version number, using the productVersion.txt file located next to the installer file. - local download_links=($(get_specific_product_version_url "$azure_feed" "$specific_version" true "$package_download_link") - $(get_specific_product_version_url "$azure_feed" "$specific_version" false "$package_download_link")) - - for download_link in "${download_links[@]}" - do - say_verbose "Checking for the existence of $download_link" - - if machine_has "curl" - then - if ! specific_product_version=$(curl -s --fail "${download_link}${feed_credential}" 2>&1); then - continue - else - echo "${specific_product_version//[$'\t\r\n']}" - return 0 - fi - - elif machine_has "wget" - then - specific_product_version=$(wget -qO- "${download_link}${feed_credential}" 2>&1) - if [ $? = 0 ]; then - echo "${specific_product_version//[$'\t\r\n']}" - return 0 - fi - fi - done - - # Getting the version number with productVersion.txt has failed. Try parsing the download link for a version number. - say_verbose "Failed to get the version using productVersion.txt file. Download link will be parsed instead." - specific_product_version="$(get_product_specific_version_from_download_link "$package_download_link" "$specific_version")" - echo "${specific_product_version//[$'\t\r\n']}" - return 0 -} - -# args: -# azure_feed - $1 -# specific_version - $2 -# is_flattened - $3 -# download link - $4 (optional) -get_specific_product_version_url() { - eval $invocation - - local azure_feed="$1" - local specific_version="$2" - local is_flattened="$3" - local package_download_link="" - if [ $# -gt 3 ]; then - local package_download_link="$4" - fi - - local pvFileName="productVersion.txt" - if [ "$is_flattened" = true ]; then - if [ -z "$runtime" ]; then - pvFileName="sdk-productVersion.txt" - elif [[ "$runtime" == "dotnet" ]]; then - pvFileName="runtime-productVersion.txt" - else - pvFileName="$runtime-productVersion.txt" - fi - fi - - local download_link=null - - if [ -z "$package_download_link" ]; then - if [[ "$runtime" == "dotnet" ]]; then - download_link="$azure_feed/Runtime/$specific_version/${pvFileName}" - elif [[ "$runtime" == "aspnetcore" ]]; then - download_link="$azure_feed/aspnetcore/Runtime/$specific_version/${pvFileName}" - elif [ -z "$runtime" ]; then - download_link="$azure_feed/Sdk/$specific_version/${pvFileName}" - else - return 1 - fi - else - download_link="${package_download_link%/*}/${pvFileName}" - fi - - say_verbose "Constructed productVersion link: $download_link" - echo "$download_link" - return 0 -} - -# args: -# download link - $1 -# specific version - $2 -get_product_specific_version_from_download_link() -{ - eval $invocation - - local download_link="$1" - local specific_version="$2" - local specific_product_version="" - - if [ -z "$download_link" ]; then - echo "$specific_version" - return 0 - fi - - #get filename - filename="${download_link##*/}" - - #product specific version follows the product name - #for filename 'dotnet-sdk-3.1.404-linux-x64.tar.gz': the product version is 3.1.404 - IFS='-' - read -ra filename_elems <<< "$filename" - count=${#filename_elems[@]} - if [[ "$count" -gt 2 ]]; then - specific_product_version="${filename_elems[2]}" - else - specific_product_version=$specific_version - fi - unset IFS; - echo "$specific_product_version" - return 0 -} - -# args: -# azure_feed - $1 -# channel - $2 -# normalized_architecture - $3 -# specific_version - $4 -construct_legacy_download_link() { - eval $invocation - - local azure_feed="$1" - local channel="$2" - local normalized_architecture="$3" - local specific_version="${4//[$'\t\r\n']}" - - local distro_specific_osname - distro_specific_osname="$(get_legacy_os_name)" || return 1 - - local legacy_download_link=null - if [[ "$runtime" == "dotnet" ]]; then - legacy_download_link="$azure_feed/Runtime/$specific_version/dotnet-$distro_specific_osname-$normalized_architecture.$specific_version.tar.gz" - elif [ -z "$runtime" ]; then - legacy_download_link="$azure_feed/Sdk/$specific_version/dotnet-dev-$distro_specific_osname-$normalized_architecture.$specific_version.tar.gz" - else - return 1 - fi - - echo "$legacy_download_link" - return 0 -} - -get_user_install_path() { - eval $invocation - - if [ ! -z "${DOTNET_INSTALL_DIR:-}" ]; then - echo "$DOTNET_INSTALL_DIR" - else - echo "$HOME/.dotnet" - fi - return 0 -} - -# args: -# install_dir - $1 -resolve_installation_path() { - eval $invocation - - local install_dir=$1 - if [ "$install_dir" = "" ]; then - local user_install_path="$(get_user_install_path)" - say_verbose "resolve_installation_path: user_install_path=$user_install_path" - echo "$user_install_path" - return 0 - fi - - echo "$install_dir" - return 0 -} - -# args: -# relative_or_absolute_path - $1 -get_absolute_path() { - eval $invocation - - local relative_or_absolute_path=$1 - echo "$(cd "$(dirname "$1")" && pwd -P)/$(basename "$1")" - return 0 -} - -# args: -# override - $1 (boolean, true or false) -get_cp_options() { - eval $invocation - - local override="$1" - local override_switch="" - - if [ "$override" = false ]; then - override_switch="-n" - - # create temporary files to check if 'cp -u' is supported - tmp_dir="$(mktemp -d)" - tmp_file="$tmp_dir/testfile" - tmp_file2="$tmp_dir/testfile2" - - touch "$tmp_file" - - # use -u instead of -n if it's available - if cp -u "$tmp_file" "$tmp_file2" 2>/dev/null; then - override_switch="-u" - fi - - # clean up - rm -f "$tmp_file" "$tmp_file2" - rm -rf "$tmp_dir" - fi - - echo "$override_switch" -} - -# args: -# input_files - stdin -# root_path - $1 -# out_path - $2 -# override - $3 -copy_files_or_dirs_from_list() { - eval $invocation - - local root_path="$(remove_trailing_slash "$1")" - local out_path="$(remove_trailing_slash "$2")" - local override="$3" - local override_switch="$(get_cp_options "$override")" - - cat | uniq | while read -r file_path; do - local path="$(remove_beginning_slash "${file_path#$root_path}")" - local target="$out_path/$path" - if [ "$override" = true ] || (! ([ -d "$target" ] || [ -e "$target" ])); then - mkdir -p "$out_path/$(dirname "$path")" - if [ -d "$target" ]; then - rm -rf "$target" - fi - cp -R $override_switch "$root_path/$path" "$target" - fi - done -} - -# args: -# zip_uri - $1 -get_remote_file_size() { - local zip_uri="$1" - - if machine_has "curl"; then - file_size=$(curl -sI "$zip_uri" | grep -i content-length | awk '{ num = $2 + 0; print num }') - elif machine_has "wget"; then - file_size=$(wget --spider --server-response -O /dev/null "$zip_uri" 2>&1 | grep -i 'Content-Length:' | awk '{ num = $2 + 0; print num }') - else - say "Neither curl nor wget is available on this system." - return - fi - - if [ -n "$file_size" ]; then - say "Remote file $zip_uri size is $file_size bytes." - echo "$file_size" - else - say_verbose "Content-Length header was not extracted for $zip_uri." - echo "" - fi -} - -# args: -# zip_path - $1 -# out_path - $2 -# remote_file_size - $3 -extract_dotnet_package() { - eval $invocation - - local zip_path="$1" - local out_path="$2" - local remote_file_size="$3" - - local temp_out_path="$(mktemp -d "$temporary_file_template")" - - local failed=false - tar -xzf "$zip_path" -C "$temp_out_path" > /dev/null || failed=true - - local folders_with_version_regex='^.*/[0-9]+\.[0-9]+[^/]+/' - find "$temp_out_path" -type f | grep -Eo "$folders_with_version_regex" | sort | copy_files_or_dirs_from_list "$temp_out_path" "$out_path" false - find "$temp_out_path" -type f | grep -Ev "$folders_with_version_regex" | copy_files_or_dirs_from_list "$temp_out_path" "$out_path" "$override_non_versioned_files" - - validate_remote_local_file_sizes "$zip_path" "$remote_file_size" - - rm -rf "$temp_out_path" - if [ -z ${keep_zip+x} ]; then - rm -f "$zip_path" && say_verbose "Temporary archive file $zip_path was removed" - fi - - if [ "$failed" = true ]; then - say_err "Extraction failed" - return 1 - fi - return 0 -} - -# args: -# remote_path - $1 -# disable_feed_credential - $2 -get_http_header() -{ - eval $invocation - local remote_path="$1" - local disable_feed_credential="$2" - - local failed=false - local response - if machine_has "curl"; then - get_http_header_curl $remote_path $disable_feed_credential || failed=true - elif machine_has "wget"; then - get_http_header_wget $remote_path $disable_feed_credential || failed=true - else - failed=true - fi - if [ "$failed" = true ]; then - say_verbose "Failed to get HTTP header: '$remote_path'." - return 1 - fi - return 0 -} - -# args: -# remote_path - $1 -# disable_feed_credential - $2 -get_http_header_curl() { - eval $invocation - local remote_path="$1" - local disable_feed_credential="$2" - - remote_path_with_credential="$remote_path" - if [ "$disable_feed_credential" = false ]; then - remote_path_with_credential+="$feed_credential" - fi - - curl_options="-I -sSL --retry 5 --retry-delay 2 --connect-timeout 15 " - curl $curl_options "$remote_path_with_credential" 2>&1 || return 1 - return 0 -} - -# args: -# remote_path - $1 -# disable_feed_credential - $2 -get_http_header_wget() { - eval $invocation - local remote_path="$1" - local disable_feed_credential="$2" - local wget_options="-q -S --spider --tries 5 " - - local wget_options_extra='' - - # Test for options that aren't supported on all wget implementations. - if [[ $(wget -h 2>&1 | grep -E 'waitretry|connect-timeout') ]]; then - wget_options_extra="--waitretry 2 --connect-timeout 15 " - else - say "wget extra options are unavailable for this environment" - fi - - remote_path_with_credential="$remote_path" - if [ "$disable_feed_credential" = false ]; then - remote_path_with_credential+="$feed_credential" - fi - - wget $wget_options $wget_options_extra "$remote_path_with_credential" 2>&1 - - return $? -} - -# args: -# remote_path - $1 -# [out_path] - $2 - stdout if not provided -download() { - eval $invocation - - local remote_path="$1" - local out_path="${2:-}" - - if [[ "$remote_path" != "http"* ]]; then - cp "$remote_path" "$out_path" - return $? - fi - - local failed=false - local attempts=0 - while [ $attempts -lt 3 ]; do - attempts=$((attempts+1)) - failed=false - if machine_has "curl"; then - downloadcurl "$remote_path" "$out_path" || failed=true - elif machine_has "wget"; then - downloadwget "$remote_path" "$out_path" || failed=true - else - say_err "Missing dependency: neither curl nor wget was found." - exit 1 - fi - - if [ "$failed" = false ] || [ $attempts -ge 3 ] || { [ ! -z $http_code ] && [ $http_code = "404" ]; }; then - break - fi - - say "Download attempt #$attempts has failed: $http_code $download_error_msg" - say "Attempt #$((attempts+1)) will start in $((attempts*10)) seconds." - sleep $((attempts*10)) - done - - if [ "$failed" = true ]; then - say_verbose "Download failed: $remote_path" - return 1 - fi - return 0 -} - -# Updates global variables $http_code and $download_error_msg -downloadcurl() { - eval $invocation - unset http_code - unset download_error_msg - local remote_path="$1" - local out_path="${2:-}" - # Append feed_credential as late as possible before calling curl to avoid logging feed_credential - # Avoid passing URI with credentials to functions: note, most of them echoing parameters of invocation in verbose output. - local remote_path_with_credential="${remote_path}${feed_credential}" - local curl_options="--retry 20 --retry-delay 2 --connect-timeout 15 -sSL -f --create-dirs " - local curl_exit_code=0; - if [ -z "$out_path" ]; then - curl_output=$(curl $curl_options "$remote_path_with_credential" 2>&1) - curl_exit_code=$? - echo "$curl_output" - else - curl_output=$(curl $curl_options -o "$out_path" "$remote_path_with_credential" 2>&1) - curl_exit_code=$? - fi - - # Regression in curl causes curl with --retry to return a 0 exit code even when it fails to download a file - https://github.com/curl/curl/issues/17554 - if [ $curl_exit_code -eq 0 ] && echo "$curl_output" | grep -q "^curl: ([0-9]*) "; then - curl_exit_code=$(echo "$curl_output" | sed 's/curl: (\([0-9]*\)).*/\1/') - fi - - if [ $curl_exit_code -gt 0 ]; then - download_error_msg="Unable to download $remote_path." - # Check for curl timeout codes - if [[ $curl_exit_code == 7 || $curl_exit_code == 28 ]]; then - download_error_msg+=" Failed to reach the server: connection timeout." - else - local disable_feed_credential=false - local response=$(get_http_header_curl $remote_path $disable_feed_credential) - http_code=$( echo "$response" | awk '/^HTTP/{print $2}' | tail -1 ) - if [[ ! -z $http_code && $http_code != 2* ]]; then - download_error_msg+=" Returned HTTP status code: $http_code." - fi - fi - say_verbose "$download_error_msg" - return 1 - fi - return 0 -} - - -# Updates global variables $http_code and $download_error_msg -downloadwget() { - eval $invocation - unset http_code - unset download_error_msg - local remote_path="$1" - local out_path="${2:-}" - # Append feed_credential as late as possible before calling wget to avoid logging feed_credential - local remote_path_with_credential="${remote_path}${feed_credential}" - local wget_options="--tries 20 " - - local wget_options_extra='' - local wget_result='' - - # Test for options that aren't supported on all wget implementations. - if [[ $(wget -h 2>&1 | grep -E 'waitretry|connect-timeout') ]]; then - wget_options_extra="--waitretry 2 --connect-timeout 15 " - else - say "wget extra options are unavailable for this environment" - fi - - if [ -z "$out_path" ]; then - wget -q $wget_options $wget_options_extra -O - "$remote_path_with_credential" 2>&1 - wget_result=$? - else - wget $wget_options $wget_options_extra -O "$out_path" "$remote_path_with_credential" 2>&1 - wget_result=$? - fi - - if [[ $wget_result != 0 ]]; then - local disable_feed_credential=false - local response=$(get_http_header_wget $remote_path $disable_feed_credential) - http_code=$( echo "$response" | awk '/^ HTTP/{print $2}' | tail -1 ) - download_error_msg="Unable to download $remote_path." - if [[ ! -z $http_code && $http_code != 2* ]]; then - download_error_msg+=" Returned HTTP status code: $http_code." - # wget exit code 4 stands for network-issue - elif [[ $wget_result == 4 ]]; then - download_error_msg+=" Failed to reach the server: connection timeout." - fi - say_verbose "$download_error_msg" - return 1 - fi - - return 0 -} - -get_download_link_from_aka_ms() { - eval $invocation - - #quality is not supported for LTS or STS channel - #STS maps to current - if [[ ! -z "$normalized_quality" && ("$normalized_channel" == "LTS" || "$normalized_channel" == "STS") ]]; then - normalized_quality="" - say_warning "Specifying quality for STS or LTS channel is not supported, the quality will be ignored." - fi - - say_verbose "Retrieving primary payload URL from aka.ms for channel: '$normalized_channel', quality: '$normalized_quality', product: '$normalized_product', os: '$normalized_os', architecture: '$normalized_architecture'." - - #construct aka.ms link - aka_ms_link="https://aka.ms/dotnet" - if [ "$internal" = true ]; then - aka_ms_link="$aka_ms_link/internal" - fi - aka_ms_link="$aka_ms_link/$normalized_channel" - if [[ ! -z "$normalized_quality" ]]; then - aka_ms_link="$aka_ms_link/$normalized_quality" - fi - aka_ms_link="$aka_ms_link/$normalized_product-$normalized_os-$normalized_architecture.tar.gz" - say_verbose "Constructed aka.ms link: '$aka_ms_link'." - - #get HTTP response - #do not pass credentials as a part of the $aka_ms_link and do not apply credentials in the get_http_header function - #otherwise the redirect link would have credentials as well - #it would result in applying credentials twice to the resulting link and thus breaking it, and in echoing credentials to the output as a part of redirect link - disable_feed_credential=true - response="$(get_http_header $aka_ms_link $disable_feed_credential)" - - say_verbose "Received response: $response" - # Get results of all the redirects. - http_codes=$( echo "$response" | awk '$1 ~ /^HTTP/ {print $2}' ) - # They all need to be 301, otherwise some links are broken (except for the last, which is not a redirect but 200 or 404). - broken_redirects=$( echo "$http_codes" | sed '$d' | grep -v '301' ) - # The response may end without final code 2xx/4xx/5xx somehow, e.g. network restrictions on www.bing.com causes redirecting to bing.com fails with connection refused. - # In this case it should not exclude the last. - last_http_code=$( echo "$http_codes" | tail -n 1 ) - if ! [[ $last_http_code =~ ^(2|4|5)[0-9][0-9]$ ]]; then - broken_redirects=$( echo "$http_codes" | grep -v '301' ) - fi - - # All HTTP codes are 301 (Moved Permanently), the redirect link exists. - if [[ -z "$broken_redirects" ]]; then - aka_ms_download_link=$( echo "$response" | awk '$1 ~ /^Location/{print $2}' | tail -1 | tr -d '\r') - - if [[ -z "$aka_ms_download_link" ]]; then - say_verbose "The aka.ms link '$aka_ms_link' is not valid: failed to get redirect location." - return 1 - fi - - say_verbose "The redirect location retrieved: '$aka_ms_download_link'." - return 0 - else - say_verbose "The aka.ms link '$aka_ms_link' is not valid: received HTTP code: $(echo "$broken_redirects" | paste -sd "," -)." - return 1 - fi -} - -get_feeds_to_use() -{ - feeds=( - "https://builds.dotnet.microsoft.com/dotnet" - "https://ci.dot.net/public" - ) - - if [[ -n "$azure_feed" ]]; then - feeds=("$azure_feed") - fi - - if [[ -n "$uncached_feed" ]]; then - feeds=("$uncached_feed") - fi -} - -# THIS FUNCTION MAY EXIT (if the determined version is already installed). -generate_download_links() { - - download_links=() - specific_versions=() - effective_versions=() - link_types=() - - # If generate_akams_links returns false, no fallback to old links. Just terminate. - # This function may also 'exit' (if the determined version is already installed). - generate_akams_links || return - - # Check other feeds only if we haven't been able to find an aka.ms link. - if [[ "${#download_links[@]}" -lt 1 ]]; then - for feed in ${feeds[@]} - do - # generate_regular_links may also 'exit' (if the determined version is already installed). - generate_regular_links $feed || return - done - fi - - if [[ "${#download_links[@]}" -eq 0 ]]; then - say_err "Failed to resolve the exact version number." - return 1 - fi - - say_verbose "Generated ${#download_links[@]} links." - for link_index in ${!download_links[@]} - do - say_verbose "Link $link_index: ${link_types[$link_index]}, ${effective_versions[$link_index]}, ${download_links[$link_index]}" - done -} - -# THIS FUNCTION MAY EXIT (if the determined version is already installed). -generate_akams_links() { - local valid_aka_ms_link=true; - - normalized_version="$(to_lowercase "$version")" - if [[ "$normalized_version" != "latest" ]] && [ -n "$normalized_quality" ]; then - say_err "Quality and Version options are not allowed to be specified simultaneously. See https://learn.microsoft.com/dotnet/core/tools/dotnet-install-script#options for details." - return 1 - fi - - if [[ -n "$json_file" || "$normalized_version" != "latest" ]]; then - # aka.ms links are not needed when exact version is specified via command or json file - return - fi - - get_download_link_from_aka_ms || valid_aka_ms_link=false - - if [[ "$valid_aka_ms_link" == true ]]; then - say_verbose "Retrieved primary payload URL from aka.ms link: '$aka_ms_download_link'." - say_verbose "Downloading using legacy url will not be attempted." - - download_link=$aka_ms_download_link - - #get version from the path - IFS='/' - read -ra pathElems <<< "$download_link" - count=${#pathElems[@]} - specific_version="${pathElems[count-2]}" - unset IFS; - say_verbose "Version: '$specific_version'." - - #Retrieve effective version - effective_version="$(get_specific_product_version "$azure_feed" "$specific_version" "$download_link")" - - # Add link info to arrays - download_links+=($download_link) - specific_versions+=($specific_version) - effective_versions+=($effective_version) - link_types+=("aka.ms") - - # Check if the SDK version is already installed. - if [[ "$dry_run" != true ]] && is_dotnet_package_installed "$install_root" "$asset_relative_path" "$effective_version"; then - say "$asset_name with version '$effective_version' is already installed." - exit 0 - fi - - return 0 - fi - - # if quality is specified - exit with error - there is no fallback approach - if [ ! -z "$normalized_quality" ]; then - say_err "Failed to locate the latest version in the channel '$normalized_channel' with '$normalized_quality' quality for '$normalized_product', os: '$normalized_os', architecture: '$normalized_architecture'." - say_err "Refer to: https://aka.ms/dotnet-os-lifecycle for information on .NET Core support." - return 1 - fi - say_verbose "Falling back to latest.version file approach." -} - -# THIS FUNCTION MAY EXIT (if the determined version is already installed) -# args: -# feed - $1 -generate_regular_links() { - local feed="$1" - local valid_legacy_download_link=true - - specific_version=$(get_specific_version_from_version "$feed" "$channel" "$normalized_architecture" "$version" "$json_file") || specific_version='0' - - if [[ "$specific_version" == '0' ]]; then - say_verbose "Failed to resolve the specific version number using feed '$feed'" - return - fi - - effective_version="$(get_specific_product_version "$feed" "$specific_version")" - say_verbose "specific_version=$specific_version" - - download_link="$(construct_download_link "$feed" "$channel" "$normalized_architecture" "$specific_version" "$normalized_os")" - say_verbose "Constructed primary named payload URL: $download_link" - - # Add link info to arrays - download_links+=($download_link) - specific_versions+=($specific_version) - effective_versions+=($effective_version) - link_types+=("primary") - - legacy_download_link="$(construct_legacy_download_link "$feed" "$channel" "$normalized_architecture" "$specific_version")" || valid_legacy_download_link=false - - if [ "$valid_legacy_download_link" = true ]; then - say_verbose "Constructed legacy named payload URL: $legacy_download_link" - - download_links+=($legacy_download_link) - specific_versions+=($specific_version) - effective_versions+=($effective_version) - link_types+=("legacy") - else - legacy_download_link="" - say_verbose "Could not construct a legacy_download_link; omitting..." - fi - - # Check if the SDK version is already installed. - if [[ "$dry_run" != true ]] && is_dotnet_package_installed "$install_root" "$asset_relative_path" "$effective_version"; then - say "$asset_name with version '$effective_version' is already installed." - exit 0 - fi -} - -print_dry_run() { - - say "Payload URLs:" - - for link_index in "${!download_links[@]}" - do - say "URL #$link_index - ${link_types[$link_index]}: ${download_links[$link_index]}" - done - - resolved_version=${specific_versions[0]} - repeatable_command="./$script_name --version "\""$resolved_version"\"" --install-dir "\""$install_root"\"" --architecture "\""$normalized_architecture"\"" --os "\""$normalized_os"\""" - - if [ ! -z "$normalized_quality" ]; then - repeatable_command+=" --quality "\""$normalized_quality"\""" - fi - - if [[ "$runtime" == "dotnet" ]]; then - repeatable_command+=" --runtime "\""dotnet"\""" - elif [[ "$runtime" == "aspnetcore" ]]; then - repeatable_command+=" --runtime "\""aspnetcore"\""" - fi - - repeatable_command+="$non_dynamic_parameters" - - if [ -n "$feed_credential" ]; then - repeatable_command+=" --feed-credential "\"""\""" - fi - - say "Repeatable invocation: $repeatable_command" -} - -calculate_vars() { - eval $invocation - - script_name=$(basename "$0") - normalized_architecture="$(get_normalized_architecture_from_architecture "$architecture")" - say_verbose "Normalized architecture: '$normalized_architecture'." - normalized_os="$(get_normalized_os "$user_defined_os")" - say_verbose "Normalized OS: '$normalized_os'." - normalized_quality="$(get_normalized_quality "$quality")" - say_verbose "Normalized quality: '$normalized_quality'." - normalized_channel="$(get_normalized_channel "$channel")" - say_verbose "Normalized channel: '$normalized_channel'." - normalized_product="$(get_normalized_product "$runtime")" - say_verbose "Normalized product: '$normalized_product'." - install_root="$(resolve_installation_path "$install_dir")" - say_verbose "InstallRoot: '$install_root'." - - normalized_architecture="$(get_normalized_architecture_for_specific_sdk_version "$version" "$normalized_channel" "$normalized_architecture")" - - if [[ "$runtime" == "dotnet" ]]; then - asset_relative_path="shared/Microsoft.NETCore.App" - asset_name=".NET Core Runtime" - elif [[ "$runtime" == "aspnetcore" ]]; then - asset_relative_path="shared/Microsoft.AspNetCore.App" - asset_name="ASP.NET Core Runtime" - elif [ -z "$runtime" ]; then - asset_relative_path="sdk" - asset_name=".NET Core SDK" - fi - - get_feeds_to_use -} - -install_dotnet() { - eval $invocation - local download_failed=false - local download_completed=false - local remote_file_size=0 - - mkdir -p "$install_root" - zip_path="${zip_path:-$(mktemp "$temporary_file_template")}" - say_verbose "Archive path: $zip_path" - - for link_index in "${!download_links[@]}" - do - download_link="${download_links[$link_index]}" - specific_version="${specific_versions[$link_index]}" - effective_version="${effective_versions[$link_index]}" - link_type="${link_types[$link_index]}" - - say "Attempting to download using $link_type link $download_link" - - # The download function will set variables $http_code and $download_error_msg in case of failure. - download_failed=false - download "$download_link" "$zip_path" 2>&1 || download_failed=true - - if [ "$download_failed" = true ]; then - case $http_code in - 404) - say "The resource at $link_type link '$download_link' is not available." - ;; - *) - say "Failed to download $link_type link '$download_link': $http_code $download_error_msg" - ;; - esac - rm -f "$zip_path" 2>&1 && say_verbose "Temporary archive file $zip_path was removed" - else - download_completed=true - break - fi - done - - if [[ "$download_completed" == false ]]; then - say_err "Could not find \`$asset_name\` with version = $specific_version" - say_err "Refer to: https://aka.ms/dotnet-os-lifecycle for information on .NET Core support" - return 1 - fi - - remote_file_size="$(get_remote_file_size "$download_link")" - - say "Extracting archive from $download_link" - extract_dotnet_package "$zip_path" "$install_root" "$remote_file_size" || return 1 - - # Check if the SDK version is installed; if not, fail the installation. - # if the version contains "RTM" or "servicing"; check if a 'release-type' SDK version is installed. - if [[ $specific_version == *"rtm"* || $specific_version == *"servicing"* ]]; then - IFS='-' - read -ra verArr <<< "$specific_version" - release_version="${verArr[0]}" - unset IFS; - say_verbose "Checking installation: version = $release_version" - if is_dotnet_package_installed "$install_root" "$asset_relative_path" "$release_version"; then - say "Installed version is $effective_version" - return 0 - fi - fi - - # Check if the standard SDK version is installed. - say_verbose "Checking installation: version = $effective_version" - if is_dotnet_package_installed "$install_root" "$asset_relative_path" "$effective_version"; then - say "Installed version is $effective_version" - return 0 - fi - - # Version verification failed. More likely something is wrong either with the downloaded content or with the verification algorithm. - say_err "Failed to verify the version of installed \`$asset_name\`.\nInstallation source: $download_link.\nInstallation location: $install_root.\nReport the bug at https://github.com/dotnet/install-scripts/issues." - say_err "\`$asset_name\` with version = $effective_version failed to install with an error." - return 1 -} - -args=("$@") - -local_version_file_relative_path="/.version" -bin_folder_relative_path="" -temporary_file_template="${TMPDIR:-/tmp}/dotnet.XXXXXXXXX" - -channel="LTS" -version="Latest" -json_file="" -install_dir="" -architecture="" -dry_run=false -no_path=false -azure_feed="" -uncached_feed="" -feed_credential="" -verbose=false -runtime="" -runtime_id="" -quality="" -internal=false -override_non_versioned_files=true -non_dynamic_parameters="" -user_defined_os="" - -while [ $# -ne 0 ] -do - name="$1" - case "$name" in - -c|--channel|-[Cc]hannel) - shift - channel="$1" - ;; - -v|--version|-[Vv]ersion) - shift - version="$1" - ;; - -q|--quality|-[Qq]uality) - shift - quality="$1" - ;; - --internal|-[Ii]nternal) - internal=true - non_dynamic_parameters+=" $name" - ;; - -i|--install-dir|-[Ii]nstall[Dd]ir) - shift - install_dir="$1" - ;; - --arch|--architecture|-[Aa]rch|-[Aa]rchitecture) - shift - architecture="$1" - ;; - --os|-[Oo][SS]) - shift - user_defined_os="$1" - ;; - --shared-runtime|-[Ss]hared[Rr]untime) - say_warning "The --shared-runtime flag is obsolete and may be removed in a future version of this script. The recommended usage is to specify '--runtime dotnet'." - if [ -z "$runtime" ]; then - runtime="dotnet" - fi - ;; - --runtime|-[Rr]untime) - shift - runtime="$1" - if [[ "$runtime" != "dotnet" ]] && [[ "$runtime" != "aspnetcore" ]]; then - say_err "Unsupported value for --runtime: '$1'. Valid values are 'dotnet' and 'aspnetcore'." - if [[ "$runtime" == "windowsdesktop" ]]; then - say_err "WindowsDesktop archives are manufactured for Windows platforms only." - fi - exit 1 - fi - ;; - --dry-run|-[Dd]ry[Rr]un) - dry_run=true - ;; - --no-path|-[Nn]o[Pp]ath) - no_path=true - non_dynamic_parameters+=" $name" - ;; - --verbose|-[Vv]erbose) - verbose=true - non_dynamic_parameters+=" $name" - ;; - --azure-feed|-[Aa]zure[Ff]eed) - shift - azure_feed="$1" - non_dynamic_parameters+=" $name "\""$1"\""" - ;; - --uncached-feed|-[Uu]ncached[Ff]eed) - shift - uncached_feed="$1" - non_dynamic_parameters+=" $name "\""$1"\""" - ;; - --feed-credential|-[Ff]eed[Cc]redential) - shift - feed_credential="$1" - #feed_credential should start with "?", for it to be added to the end of the link. - #adding "?" at the beginning of the feed_credential if needed. - [[ -z "$(echo $feed_credential)" ]] || [[ $feed_credential == \?* ]] || feed_credential="?$feed_credential" - ;; - --runtime-id|-[Rr]untime[Ii]d) - shift - runtime_id="$1" - non_dynamic_parameters+=" $name "\""$1"\""" - say_warning "Use of --runtime-id is obsolete and should be limited to the versions below 2.1. To override architecture, use --architecture option instead. To override OS, use --os option instead." - ;; - --jsonfile|-[Jj][Ss]on[Ff]ile) - shift - json_file="$1" - ;; - --skip-non-versioned-files|-[Ss]kip[Nn]on[Vv]ersioned[Ff]iles) - override_non_versioned_files=false - non_dynamic_parameters+=" $name" - ;; - --keep-zip|-[Kk]eep[Zz]ip) - keep_zip=true - non_dynamic_parameters+=" $name" - ;; - --zip-path|-[Zz]ip[Pp]ath) - shift - zip_path="$1" - ;; - -?|--?|-h|--help|-[Hh]elp) - script_name="dotnet-install.sh" - echo ".NET Tools Installer" - echo "Usage:" - echo " # Install a .NET SDK of a given Quality from a given Channel" - echo " $script_name [-c|--channel ] [-q|--quality ]" - echo " # Install a .NET SDK of a specific public version" - echo " $script_name [-v|--version ]" - echo " $script_name -h|-?|--help" - echo "" - echo "$script_name is a simple command line interface for obtaining dotnet cli." - echo " Note that the intended use of this script is for Continuous Integration (CI) scenarios, where:" - echo " - The SDK needs to be installed without user interaction and without admin rights." - echo " - The SDK installation doesn't need to persist across multiple CI runs." - echo " To set up a development environment or to run apps, use installers rather than this script. Visit https://dotnet.microsoft.com/download to get the installer." - echo "" - echo "Options:" - echo " -c,--channel Download from the channel specified, Defaults to \`$channel\`." - echo " -Channel" - echo " Possible values:" - echo " - STS - the most recent Standard Term Support release" - echo " - LTS - the most recent Long Term Support release" - echo " - 2-part version in a format A.B - represents a specific release" - echo " examples: 2.0; 1.0" - echo " - 3-part version in a format A.B.Cxx - represents a specific SDK release" - echo " examples: 5.0.1xx, 5.0.2xx." - echo " Supported since 5.0 release" - echo " Warning: Value 'Current' is deprecated for the Channel parameter. Use 'STS' instead." - echo " Note: The version parameter overrides the channel parameter when any version other than 'latest' is used." - echo " -v,--version Use specific VERSION, Defaults to \`$version\`." - echo " -Version" - echo " Possible values:" - echo " - latest - the latest build on specific channel" - echo " - 3-part version in a format A.B.C - represents specific version of build" - echo " examples: 2.0.0-preview2-006120; 1.1.0" - echo " -q,--quality Download the latest build of specified quality in the channel." - echo " -Quality" - echo " The possible values are: daily, preview, GA." - echo " Works only in combination with channel. Not applicable for STS and LTS channels and will be ignored if those channels are used." - echo " For SDK use channel in A.B.Cxx format. Using quality for SDK together with channel in A.B format is not supported." - echo " Supported since 5.0 release." - echo " Note: The version parameter overrides the channel parameter when any version other than 'latest' is used, and therefore overrides the quality." - echo " --internal,-Internal Download internal builds. Requires providing credentials via --feed-credential parameter." - echo " --feed-credential Token to access Azure feed. Used as a query string to append to the Azure feed." - echo " -FeedCredential This parameter typically is not specified." - echo " -i,--install-dir Install under specified location (see Install Location below)" - echo " -InstallDir" - echo " --architecture Architecture of dotnet binaries to be installed, Defaults to \`$architecture\`." - echo " --arch,-Architecture,-Arch" - echo " Possible values: x64, arm, arm64, s390x, ppc64le and loongarch64" - echo " --os Specifies operating system to be used when selecting the installer." - echo " Overrides the OS determination approach used by the script. Supported values: osx, linux, linux-musl, freebsd, rhel.6." - echo " In case any other value is provided, the platform will be determined by the script based on machine configuration." - echo " Not supported for legacy links. Use --runtime-id to specify platform for legacy links." - echo " Refer to: https://aka.ms/dotnet-os-lifecycle for more information." - echo " --runtime Installs a shared runtime only, without the SDK." - echo " -Runtime" - echo " Possible values:" - echo " - dotnet - the Microsoft.NETCore.App shared runtime" - echo " - aspnetcore - the Microsoft.AspNetCore.App shared runtime" - echo " --dry-run,-DryRun Do not perform installation. Display download link." - echo " --no-path, -NoPath Do not set PATH for the current process." - echo " --verbose,-Verbose Display diagnostics information." - echo " --azure-feed,-AzureFeed For internal use only." - echo " Allows using a different storage to download SDK archives from." - echo " --uncached-feed,-UncachedFeed For internal use only." - echo " Allows using a different storage to download SDK archives from." - echo " --skip-non-versioned-files Skips non-versioned files if they already exist, such as the dotnet executable." - echo " -SkipNonVersionedFiles" - echo " --jsonfile Determines the SDK version from a user specified global.json file." - echo " Note: global.json must have a value for 'SDK:Version'" - echo " --keep-zip,-KeepZip If set, downloaded file is kept." - echo " --zip-path, -ZipPath If set, downloaded file is stored at the specified path." - echo " -?,--?,-h,--help,-Help Shows this help message" - echo "" - echo "Install Location:" - echo " Location is chosen in following order:" - echo " - --install-dir option" - echo " - Environmental variable DOTNET_INSTALL_DIR" - echo " - $HOME/.dotnet" - exit 0 - ;; - *) - say_err "Unknown argument \`$name\`" - exit 1 - ;; - esac - - shift -done - -say_verbose "Note that the intended use of this script is for Continuous Integration (CI) scenarios, where:" -say_verbose "- The SDK needs to be installed without user interaction and without admin rights." -say_verbose "- The SDK installation doesn't need to persist across multiple CI runs." -say_verbose "To set up a development environment or to run apps, use installers rather than this script. Visit https://dotnet.microsoft.com/download to get the installer.\n" - -if [ "$internal" = true ] && [ -z "$(echo $feed_credential)" ]; then - message="Provide credentials via --feed-credential parameter." - if [ "$dry_run" = true ]; then - say_warning "$message" - else - say_err "$message" - exit 1 - fi -fi - -check_min_reqs -calculate_vars -# generate_regular_links call below will 'exit' if the determined version is already installed. -generate_download_links - -if [[ "$dry_run" = true ]]; then - print_dry_run - exit 0 -fi - -install_dotnet - -bin_path="$(get_absolute_path "$(combine_paths "$install_root" "$bin_folder_relative_path")")" -if [ "$no_path" = false ]; then - say "Adding to current process PATH: \`$bin_path\`. Note: This change will be visible only when sourcing script." - export PATH="$bin_path":"$PATH" -else - say "Binaries of dotnet can be found in $bin_path" -fi - -say "Note that the script does not resolve dependencies during installation." -say "To check the list of dependencies, go to https://learn.microsoft.com/dotnet/core/install, select your operating system and check the \"Dependencies\" section." -say "Installation finished successfully." diff --git a/dotnet-install.sh.1 b/dotnet-install.sh.1 deleted file mode 100644 index 034d2df..0000000 --- a/dotnet-install.sh.1 +++ /dev/null @@ -1,1888 +0,0 @@ -#!/usr/bin/env bash -# Copyright (c) .NET Foundation and contributors. All rights reserved. -# Licensed under the MIT license. See LICENSE file in the project root for full license information. -# - -# Stop script on NZEC -set -e -# Stop script if unbound variable found (use ${var:-} if intentional) -set -u -# By default cmd1 | cmd2 returns exit code of cmd2 regardless of cmd1 success -# This is causing it to fail -set -o pipefail - -# Use in the the functions: eval $invocation -invocation='say_verbose "Calling: ${yellow:-}${FUNCNAME[0]} ${green:-}$*${normal:-}"' - -# standard output may be used as a return value in the functions -# we need a way to write text on the screen in the functions so that -# it won't interfere with the return value. -# Exposing stream 3 as a pipe to standard output of the script itself -exec 3>&1 - -# Setup some colors to use. These need to work in fairly limited shells, like the Ubuntu Docker container where there are only 8 colors. -# See if stdout is a terminal -if [ -t 1 ] && command -v tput > /dev/null; then - # see if it supports colors - ncolors=$(tput colors || echo 0) - if [ -n "$ncolors" ] && [ $ncolors -ge 8 ]; then - bold="$(tput bold || echo)" - normal="$(tput sgr0 || echo)" - black="$(tput setaf 0 || echo)" - red="$(tput setaf 1 || echo)" - green="$(tput setaf 2 || echo)" - yellow="$(tput setaf 3 || echo)" - blue="$(tput setaf 4 || echo)" - magenta="$(tput setaf 5 || echo)" - cyan="$(tput setaf 6 || echo)" - white="$(tput setaf 7 || echo)" - fi -fi - -say_warning() { - printf "%b\n" "${yellow:-}dotnet_install: Warning: $1${normal:-}" >&3 -} - -say_err() { - printf "%b\n" "${red:-}dotnet_install: Error: $1${normal:-}" >&2 -} - -say() { - # using stream 3 (defined in the beginning) to not interfere with stdout of functions - # which may be used as return value - printf "%b\n" "${cyan:-}dotnet-install:${normal:-} $1" >&3 -} - -say_verbose() { - if [ "$verbose" = true ]; then - say "$1" - fi -} - -# This platform list is finite - if the SDK/Runtime has supported Linux distribution-specific assets, -# then and only then should the Linux distribution appear in this list. -# Adding a Linux distribution to this list does not imply distribution-specific support. -get_legacy_os_name_from_platform() { - eval $invocation - - platform="$1" - case "$platform" in - "centos.7") - echo "centos" - return 0 - ;; - "debian.8") - echo "debian" - return 0 - ;; - "debian.9") - echo "debian.9" - return 0 - ;; - "fedora.23") - echo "fedora.23" - return 0 - ;; - "fedora.24") - echo "fedora.24" - return 0 - ;; - "fedora.27") - echo "fedora.27" - return 0 - ;; - "fedora.28") - echo "fedora.28" - return 0 - ;; - "opensuse.13.2") - echo "opensuse.13.2" - return 0 - ;; - "opensuse.42.1") - echo "opensuse.42.1" - return 0 - ;; - "opensuse.42.3") - echo "opensuse.42.3" - return 0 - ;; - "rhel.7"*) - echo "rhel" - return 0 - ;; - "ubuntu.14.04") - echo "ubuntu" - return 0 - ;; - "ubuntu.16.04") - echo "ubuntu.16.04" - return 0 - ;; - "ubuntu.16.10") - echo "ubuntu.16.10" - return 0 - ;; - "ubuntu.18.04") - echo "ubuntu.18.04" - return 0 - ;; - "alpine.3.4.3") - echo "alpine" - return 0 - ;; - esac - return 1 -} - -get_legacy_os_name() { - eval $invocation - - local uname=$(uname) - if [ "$uname" = "Darwin" ]; then - echo "osx" - return 0 - elif [ -n "$runtime_id" ]; then - echo $(get_legacy_os_name_from_platform "${runtime_id%-*}" || echo "${runtime_id%-*}") - return 0 - else - if [ -e /etc/os-release ]; then - . /etc/os-release - os=$(get_legacy_os_name_from_platform "$ID${VERSION_ID:+.${VERSION_ID}}" || echo "") - if [ -n "$os" ]; then - echo "$os" - return 0 - fi - fi - fi - - say_verbose "Distribution specific OS name and version could not be detected: UName = $uname" - return 1 -} - -get_linux_platform_name() { - eval $invocation - - if [ -n "$runtime_id" ]; then - echo "${runtime_id%-*}" - return 0 - else - if [ -e /etc/os-release ]; then - . /etc/os-release - echo "$ID${VERSION_ID:+.${VERSION_ID}}" - return 0 - elif [ -e /etc/redhat-release ]; then - local redhatRelease=$(&1 || true) | grep -q musl -} - -get_current_os_name() { - eval $invocation - - local uname=$(uname) - if [ "$uname" = "Darwin" ]; then - echo "osx" - return 0 - elif [ "$uname" = "FreeBSD" ]; then - echo "freebsd" - return 0 - elif [ "$uname" = "Linux" ]; then - local linux_platform_name="" - linux_platform_name="$(get_linux_platform_name)" || true - - if [ "$linux_platform_name" = "rhel.6" ]; then - echo $linux_platform_name - return 0 - elif is_musl_based_distro; then - echo "linux-musl" - return 0 - elif [ "$linux_platform_name" = "linux-musl" ]; then - echo "linux-musl" - return 0 - else - echo "linux" - return 0 - fi - fi - - say_err "OS name could not be detected: UName = $uname" - return 1 -} - -machine_has() { - eval $invocation - - command -v "$1" > /dev/null 2>&1 - return $? -} - -check_min_reqs() { - local hasMinimum=false - if machine_has "curl"; then - hasMinimum=true - elif machine_has "wget"; then - hasMinimum=true - fi - - if [ "$hasMinimum" = "false" ]; then - say_err "curl (recommended) or wget are required to download dotnet. Install missing prerequisite to proceed." - return 1 - fi - return 0 -} - -# args: -# input - $1 -to_lowercase() { - #eval $invocation - - echo "$1" | tr '[:upper:]' '[:lower:]' - return 0 -} - -# args: -# input - $1 -remove_trailing_slash() { - #eval $invocation - - local input="${1:-}" - echo "${input%/}" - return 0 -} - -# args: -# input - $1 -remove_beginning_slash() { - #eval $invocation - - local input="${1:-}" - echo "${input#/}" - return 0 -} - -# args: -# root_path - $1 -# child_path - $2 - this parameter can be empty -combine_paths() { - eval $invocation - - # TODO: Consider making it work with any number of paths. For now: - if [ ! -z "${3:-}" ]; then - say_err "combine_paths: Function takes two parameters." - return 1 - fi - - local root_path="$(remove_trailing_slash "$1")" - local child_path="$(remove_beginning_slash "${2:-}")" - say_verbose "combine_paths: root_path=$root_path" - say_verbose "combine_paths: child_path=$child_path" - echo "$root_path/$child_path" - return 0 -} - -get_machine_architecture() { - eval $invocation - - if command -v uname > /dev/null; then - CPUName=$(uname -m) - case $CPUName in - armv1*|armv2*|armv3*|armv4*|armv5*|armv6*) - echo "armv6-or-below" - return 0 - ;; - armv*l) - echo "arm" - return 0 - ;; - aarch64|arm64) - if [ "$(getconf LONG_BIT)" -lt 64 ]; then - # This is 32-bit OS running on 64-bit CPU (for example Raspberry Pi OS) - echo "arm" - return 0 - fi - echo "arm64" - return 0 - ;; - s390x) - echo "s390x" - return 0 - ;; - ppc64le) - echo "ppc64le" - return 0 - ;; - loongarch64) - echo "loongarch64" - return 0 - ;; - riscv64) - echo "riscv64" - return 0 - ;; - powerpc|ppc) - echo "ppc" - return 0 - ;; - esac - fi - - # Always default to 'x64' - echo "x64" - return 0 -} - -# args: -# architecture - $1 -get_normalized_architecture_from_architecture() { - eval $invocation - - local architecture="$(to_lowercase "$1")" - - if [[ $architecture == \ ]]; then - machine_architecture="$(get_machine_architecture)" - if [[ "$machine_architecture" == "armv6-or-below" ]]; then - say_err "Architecture \`$machine_architecture\` not supported. If you think this is a bug, report it at https://github.com/dotnet/install-scripts/issues" - return 1 - fi - - echo $machine_architecture - return 0 - fi - - case "$architecture" in - amd64|x64) - echo "x64" - return 0 - ;; - arm) - echo "arm" - return 0 - ;; - arm64) - echo "arm64" - return 0 - ;; - s390x) - echo "s390x" - return 0 - ;; - ppc64le) - echo "ppc64le" - return 0 - ;; - loongarch64) - echo "loongarch64" - return 0 - ;; - esac - - say_err "Architecture \`$architecture\` not supported. If you think this is a bug, report it at https://github.com/dotnet/install-scripts/issues" - return 1 -} - -# args: -# version - $1 -# channel - $2 -# architecture - $3 -get_normalized_architecture_for_specific_sdk_version() { - eval $invocation - - local is_version_support_arm64="$(is_arm64_supported "$1")" - local is_channel_support_arm64="$(is_arm64_supported "$2")" - local architecture="$3"; - local osname="$(get_current_os_name)" - - if [ "$osname" == "osx" ] && [ "$architecture" == "arm64" ] && { [ "$is_version_support_arm64" = false ] || [ "$is_channel_support_arm64" = false ]; }; then - #check if rosetta is installed - if [ "$(/usr/bin/pgrep oahd >/dev/null 2>&1;echo $?)" -eq 0 ]; then - say_verbose "Changing user architecture from '$architecture' to 'x64' because .NET SDKs prior to version 6.0 do not support arm64." - echo "x64" - return 0; - else - say_err "Architecture \`$architecture\` is not supported for .NET SDK version \`$version\`. Please install Rosetta to allow emulation of the \`$architecture\` .NET SDK on this platform" - return 1 - fi - fi - - echo "$architecture" - return 0 -} - -# args: -# version or channel - $1 -is_arm64_supported() { - # Extract the major version by splitting on the dot - major_version="${1%%.*}" - - # Check if the major version is a valid number and less than 6 - case "$major_version" in - [0-9]*) - if [ "$major_version" -lt 6 ]; then - echo false - return 0 - fi - ;; - esac - - echo true - return 0 -} - -# args: -# user_defined_os - $1 -get_normalized_os() { - eval $invocation - - local osname="$(to_lowercase "$1")" - if [ ! -z "$osname" ]; then - case "$osname" in - osx | freebsd | rhel.6 | linux-musl | linux) - echo "$osname" - return 0 - ;; - macos) - osname='osx' - echo "$osname" - return 0 - ;; - *) - say_err "'$user_defined_os' is not a supported value for --os option, supported values are: osx, macos, linux, linux-musl, freebsd, rhel.6. If you think this is a bug, report it at https://github.com/dotnet/install-scripts/issues." - return 1 - ;; - esac - else - osname="$(get_current_os_name)" || return 1 - fi - echo "$osname" - return 0 -} - -# args: -# quality - $1 -get_normalized_quality() { - eval $invocation - - local quality="$(to_lowercase "$1")" - if [ ! -z "$quality" ]; then - case "$quality" in - daily | preview) - echo "$quality" - return 0 - ;; - ga) - #ga quality is available without specifying quality, so normalizing it to empty - return 0 - ;; - *) - say_err "'$quality' is not a supported value for --quality option. Supported values are: daily, preview, ga. If you think this is a bug, report it at https://github.com/dotnet/install-scripts/issues." - return 1 - ;; - esac - fi - return 0 -} - -# args: -# channel - $1 -get_normalized_channel() { - eval $invocation - - local channel="$(to_lowercase "$1")" - - if [[ $channel == current ]]; then - say_warning 'Value "Current" is deprecated for -Channel option. Use "STS" instead.' - fi - - if [[ $channel == release/* ]]; then - say_warning 'Using branch name with -Channel option is no longer supported with newer releases. Use -Quality option with a channel in X.Y format instead.'; - fi - - if [ ! -z "$channel" ]; then - case "$channel" in - lts) - echo "LTS" - return 0 - ;; - sts) - echo "STS" - return 0 - ;; - current) - echo "STS" - return 0 - ;; - *) - echo "$channel" - return 0 - ;; - esac - fi - - return 0 -} - -# args: -# runtime - $1 -get_normalized_product() { - eval $invocation - - local product="" - local runtime="$(to_lowercase "$1")" - if [[ "$runtime" == "dotnet" ]]; then - product="dotnet-runtime" - elif [[ "$runtime" == "aspnetcore" ]]; then - product="aspnetcore-runtime" - elif [ -z "$runtime" ]; then - product="dotnet-sdk" - fi - echo "$product" - return 0 -} - -# The version text returned from the feeds is a 1-line or 2-line string: -# For the SDK and the dotnet runtime (2 lines): -# Line 1: # commit_hash -# Line 2: # 4-part version -# For the aspnetcore runtime (1 line): -# Line 1: # 4-part version - -# args: -# version_text - stdin -get_version_from_latestversion_file_content() { - eval $invocation - - cat | tail -n 1 | sed 's/\r$//' - return 0 -} - -# args: -# install_root - $1 -# relative_path_to_package - $2 -# specific_version - $3 -is_dotnet_package_installed() { - eval $invocation - - local install_root="$1" - local relative_path_to_package="$2" - local specific_version="${3//[$'\t\r\n']}" - - local dotnet_package_path="$(combine_paths "$(combine_paths "$install_root" "$relative_path_to_package")" "$specific_version")" - say_verbose "is_dotnet_package_installed: dotnet_package_path=$dotnet_package_path" - - if [ -d "$dotnet_package_path" ]; then - return 0 - else - return 1 - fi -} - -# args: -# downloaded file - $1 -# remote_file_size - $2 -validate_remote_local_file_sizes() -{ - eval $invocation - - local downloaded_file="$1" - local remote_file_size="$2" - local file_size='' - - if [[ "$OSTYPE" == "linux-gnu"* ]]; then - file_size="$(stat -c '%s' "$downloaded_file")" - elif [[ "$OSTYPE" == "darwin"* ]]; then - # hardcode in order to avoid conflicts with GNU stat - file_size="$(/usr/bin/stat -f '%z' "$downloaded_file")" - fi - - if [ -n "$file_size" ]; then - say "Downloaded file size is $file_size bytes." - - if [ -n "$remote_file_size" ] && [ -n "$file_size" ]; then - if [ "$remote_file_size" -ne "$file_size" ]; then - say "The remote and local file sizes are not equal. The remote file size is $remote_file_size bytes and the local size is $file_size bytes. The local package may be corrupted." - else - say "The remote and local file sizes are equal." - fi - fi - - else - say "Either downloaded or local package size can not be measured. One of them may be corrupted." - fi -} - -# args: -# azure_feed - $1 -# channel - $2 -# normalized_architecture - $3 -get_version_from_latestversion_file() { - eval $invocation - - local azure_feed="$1" - local channel="$2" - local normalized_architecture="$3" - - local version_file_url=null - if [[ "$runtime" == "dotnet" ]]; then - version_file_url="$azure_feed/Runtime/$channel/latest.version" - elif [[ "$runtime" == "aspnetcore" ]]; then - version_file_url="$azure_feed/aspnetcore/Runtime/$channel/latest.version" - elif [ -z "$runtime" ]; then - version_file_url="$azure_feed/Sdk/$channel/latest.version" - else - say_err "Invalid value for \$runtime" - return 1 - fi - say_verbose "get_version_from_latestversion_file: latest url: $version_file_url" - - download "$version_file_url" || return $? - return 0 -} - -# args: -# json_file - $1 -parse_globaljson_file_for_version() { - eval $invocation - - local json_file="$1" - if [ ! -f "$json_file" ]; then - say_err "Unable to find \`$json_file\`" - return 1 - fi - - sdk_section=$(cat $json_file | tr -d "\r" | awk '/"sdk"/,/}/') - if [ -z "$sdk_section" ]; then - say_err "Unable to parse the SDK node in \`$json_file\`" - return 1 - fi - - sdk_list=$(echo $sdk_section | awk -F"[{}]" '{print $2}') - sdk_list=${sdk_list//[\" ]/} - sdk_list=${sdk_list//,/$'\n'} - - local version_info="" - while read -r line; do - IFS=: - while read -r key value; do - if [[ "$key" == "version" ]]; then - version_info=$value - fi - done <<< "$line" - done <<< "$sdk_list" - if [ -z "$version_info" ]; then - say_err "Unable to find the SDK:version node in \`$json_file\`" - return 1 - fi - - unset IFS; - echo "$version_info" - return 0 -} - -# args: -# azure_feed - $1 -# channel - $2 -# normalized_architecture - $3 -# version - $4 -# json_file - $5 -get_specific_version_from_version() { - eval $invocation - - local azure_feed="$1" - local channel="$2" - local normalized_architecture="$3" - local version="$(to_lowercase "$4")" - local json_file="$5" - - if [ -z "$json_file" ]; then - if [[ "$version" == "latest" ]]; then - local version_info - version_info="$(get_version_from_latestversion_file "$azure_feed" "$channel" "$normalized_architecture" false)" || return 1 - say_verbose "get_specific_version_from_version: version_info=$version_info" - echo "$version_info" | get_version_from_latestversion_file_content - return 0 - else - echo "$version" - return 0 - fi - else - local version_info - version_info="$(parse_globaljson_file_for_version "$json_file")" || return 1 - echo "$version_info" - return 0 - fi -} - -# args: -# azure_feed - $1 -# channel - $2 -# normalized_architecture - $3 -# specific_version - $4 -# normalized_os - $5 -construct_download_link() { - eval $invocation - - local azure_feed="$1" - local channel="$2" - local normalized_architecture="$3" - local specific_version="${4//[$'\t\r\n']}" - local specific_product_version="$(get_specific_product_version "$1" "$4")" - local osname="$5" - - local download_link=null - if [[ "$runtime" == "dotnet" ]]; then - download_link="$azure_feed/Runtime/$specific_version/dotnet-runtime-$specific_product_version-$osname-$normalized_architecture.tar.gz" - elif [[ "$runtime" == "aspnetcore" ]]; then - download_link="$azure_feed/aspnetcore/Runtime/$specific_version/aspnetcore-runtime-$specific_product_version-$osname-$normalized_architecture.tar.gz" - elif [ -z "$runtime" ]; then - download_link="$azure_feed/Sdk/$specific_version/dotnet-sdk-$specific_product_version-$osname-$normalized_architecture.tar.gz" - else - return 1 - fi - - echo "$download_link" - return 0 -} - -# args: -# azure_feed - $1 -# specific_version - $2 -# download link - $3 (optional) -get_specific_product_version() { - # If we find a 'productVersion.txt' at the root of any folder, we'll use its contents - # to resolve the version of what's in the folder, superseding the specified version. - # if 'productVersion.txt' is missing but download link is already available, product version will be taken from download link - eval $invocation - - local azure_feed="$1" - local specific_version="${2//[$'\t\r\n']}" - local package_download_link="" - if [ $# -gt 2 ]; then - local package_download_link="$3" - fi - local specific_product_version=null - - # Try to get the version number, using the productVersion.txt file located next to the installer file. - local download_links=($(get_specific_product_version_url "$azure_feed" "$specific_version" true "$package_download_link") - $(get_specific_product_version_url "$azure_feed" "$specific_version" false "$package_download_link")) - - for download_link in "${download_links[@]}" - do - say_verbose "Checking for the existence of $download_link" - - if machine_has "curl" - then - if ! specific_product_version=$(curl -s --fail "${download_link}${feed_credential}" 2>&1); then - continue - else - echo "${specific_product_version//[$'\t\r\n']}" - return 0 - fi - - elif machine_has "wget" - then - specific_product_version=$(wget -qO- "${download_link}${feed_credential}" 2>&1) - if [ $? = 0 ]; then - echo "${specific_product_version//[$'\t\r\n']}" - return 0 - fi - fi - done - - # Getting the version number with productVersion.txt has failed. Try parsing the download link for a version number. - say_verbose "Failed to get the version using productVersion.txt file. Download link will be parsed instead." - specific_product_version="$(get_product_specific_version_from_download_link "$package_download_link" "$specific_version")" - echo "${specific_product_version//[$'\t\r\n']}" - return 0 -} - -# args: -# azure_feed - $1 -# specific_version - $2 -# is_flattened - $3 -# download link - $4 (optional) -get_specific_product_version_url() { - eval $invocation - - local azure_feed="$1" - local specific_version="$2" - local is_flattened="$3" - local package_download_link="" - if [ $# -gt 3 ]; then - local package_download_link="$4" - fi - - local pvFileName="productVersion.txt" - if [ "$is_flattened" = true ]; then - if [ -z "$runtime" ]; then - pvFileName="sdk-productVersion.txt" - elif [[ "$runtime" == "dotnet" ]]; then - pvFileName="runtime-productVersion.txt" - else - pvFileName="$runtime-productVersion.txt" - fi - fi - - local download_link=null - - if [ -z "$package_download_link" ]; then - if [[ "$runtime" == "dotnet" ]]; then - download_link="$azure_feed/Runtime/$specific_version/${pvFileName}" - elif [[ "$runtime" == "aspnetcore" ]]; then - download_link="$azure_feed/aspnetcore/Runtime/$specific_version/${pvFileName}" - elif [ -z "$runtime" ]; then - download_link="$azure_feed/Sdk/$specific_version/${pvFileName}" - else - return 1 - fi - else - download_link="${package_download_link%/*}/${pvFileName}" - fi - - say_verbose "Constructed productVersion link: $download_link" - echo "$download_link" - return 0 -} - -# args: -# download link - $1 -# specific version - $2 -get_product_specific_version_from_download_link() -{ - eval $invocation - - local download_link="$1" - local specific_version="$2" - local specific_product_version="" - - if [ -z "$download_link" ]; then - echo "$specific_version" - return 0 - fi - - #get filename - filename="${download_link##*/}" - - #product specific version follows the product name - #for filename 'dotnet-sdk-3.1.404-linux-x64.tar.gz': the product version is 3.1.404 - IFS='-' - read -ra filename_elems <<< "$filename" - count=${#filename_elems[@]} - if [[ "$count" -gt 2 ]]; then - specific_product_version="${filename_elems[2]}" - else - specific_product_version=$specific_version - fi - unset IFS; - echo "$specific_product_version" - return 0 -} - -# args: -# azure_feed - $1 -# channel - $2 -# normalized_architecture - $3 -# specific_version - $4 -construct_legacy_download_link() { - eval $invocation - - local azure_feed="$1" - local channel="$2" - local normalized_architecture="$3" - local specific_version="${4//[$'\t\r\n']}" - - local distro_specific_osname - distro_specific_osname="$(get_legacy_os_name)" || return 1 - - local legacy_download_link=null - if [[ "$runtime" == "dotnet" ]]; then - legacy_download_link="$azure_feed/Runtime/$specific_version/dotnet-$distro_specific_osname-$normalized_architecture.$specific_version.tar.gz" - elif [ -z "$runtime" ]; then - legacy_download_link="$azure_feed/Sdk/$specific_version/dotnet-dev-$distro_specific_osname-$normalized_architecture.$specific_version.tar.gz" - else - return 1 - fi - - echo "$legacy_download_link" - return 0 -} - -get_user_install_path() { - eval $invocation - - if [ ! -z "${DOTNET_INSTALL_DIR:-}" ]; then - echo "$DOTNET_INSTALL_DIR" - else - echo "$HOME/.dotnet" - fi - return 0 -} - -# args: -# install_dir - $1 -resolve_installation_path() { - eval $invocation - - local install_dir=$1 - if [ "$install_dir" = "" ]; then - local user_install_path="$(get_user_install_path)" - say_verbose "resolve_installation_path: user_install_path=$user_install_path" - echo "$user_install_path" - return 0 - fi - - echo "$install_dir" - return 0 -} - -# args: -# relative_or_absolute_path - $1 -get_absolute_path() { - eval $invocation - - local relative_or_absolute_path=$1 - echo "$(cd "$(dirname "$1")" && pwd -P)/$(basename "$1")" - return 0 -} - -# args: -# override - $1 (boolean, true or false) -get_cp_options() { - eval $invocation - - local override="$1" - local override_switch="" - - if [ "$override" = false ]; then - override_switch="-n" - - # create temporary files to check if 'cp -u' is supported - tmp_dir="$(mktemp -d)" - tmp_file="$tmp_dir/testfile" - tmp_file2="$tmp_dir/testfile2" - - touch "$tmp_file" - - # use -u instead of -n if it's available - if cp -u "$tmp_file" "$tmp_file2" 2>/dev/null; then - override_switch="-u" - fi - - # clean up - rm -f "$tmp_file" "$tmp_file2" - rm -rf "$tmp_dir" - fi - - echo "$override_switch" -} - -# args: -# input_files - stdin -# root_path - $1 -# out_path - $2 -# override - $3 -copy_files_or_dirs_from_list() { - eval $invocation - - local root_path="$(remove_trailing_slash "$1")" - local out_path="$(remove_trailing_slash "$2")" - local override="$3" - local override_switch="$(get_cp_options "$override")" - - cat | uniq | while read -r file_path; do - local path="$(remove_beginning_slash "${file_path#$root_path}")" - local target="$out_path/$path" - if [ "$override" = true ] || (! ([ -d "$target" ] || [ -e "$target" ])); then - mkdir -p "$out_path/$(dirname "$path")" - if [ -d "$target" ]; then - rm -rf "$target" - fi - cp -R $override_switch "$root_path/$path" "$target" - fi - done -} - -# args: -# zip_uri - $1 -get_remote_file_size() { - local zip_uri="$1" - - if machine_has "curl"; then - file_size=$(curl -sI "$zip_uri" | grep -i content-length | awk '{ num = $2 + 0; print num }') - elif machine_has "wget"; then - file_size=$(wget --spider --server-response -O /dev/null "$zip_uri" 2>&1 | grep -i 'Content-Length:' | awk '{ num = $2 + 0; print num }') - else - say "Neither curl nor wget is available on this system." - return - fi - - if [ -n "$file_size" ]; then - say "Remote file $zip_uri size is $file_size bytes." - echo "$file_size" - else - say_verbose "Content-Length header was not extracted for $zip_uri." - echo "" - fi -} - -# args: -# zip_path - $1 -# out_path - $2 -# remote_file_size - $3 -extract_dotnet_package() { - eval $invocation - - local zip_path="$1" - local out_path="$2" - local remote_file_size="$3" - - local temp_out_path="$(mktemp -d "$temporary_file_template")" - - local failed=false - tar -xzf "$zip_path" -C "$temp_out_path" > /dev/null || failed=true - - local folders_with_version_regex='^.*/[0-9]+\.[0-9]+[^/]+/' - find "$temp_out_path" -type f | grep -Eo "$folders_with_version_regex" | sort | copy_files_or_dirs_from_list "$temp_out_path" "$out_path" false - find "$temp_out_path" -type f | grep -Ev "$folders_with_version_regex" | copy_files_or_dirs_from_list "$temp_out_path" "$out_path" "$override_non_versioned_files" - - validate_remote_local_file_sizes "$zip_path" "$remote_file_size" - - rm -rf "$temp_out_path" - if [ -z ${keep_zip+x} ]; then - rm -f "$zip_path" && say_verbose "Temporary archive file $zip_path was removed" - fi - - if [ "$failed" = true ]; then - say_err "Extraction failed" - return 1 - fi - return 0 -} - -# args: -# remote_path - $1 -# disable_feed_credential - $2 -get_http_header() -{ - eval $invocation - local remote_path="$1" - local disable_feed_credential="$2" - - local failed=false - local response - if machine_has "curl"; then - get_http_header_curl $remote_path $disable_feed_credential || failed=true - elif machine_has "wget"; then - get_http_header_wget $remote_path $disable_feed_credential || failed=true - else - failed=true - fi - if [ "$failed" = true ]; then - say_verbose "Failed to get HTTP header: '$remote_path'." - return 1 - fi - return 0 -} - -# args: -# remote_path - $1 -# disable_feed_credential - $2 -get_http_header_curl() { - eval $invocation - local remote_path="$1" - local disable_feed_credential="$2" - - remote_path_with_credential="$remote_path" - if [ "$disable_feed_credential" = false ]; then - remote_path_with_credential+="$feed_credential" - fi - - curl_options="-I -sSL --retry 5 --retry-delay 2 --connect-timeout 15 " - curl $curl_options "$remote_path_with_credential" 2>&1 || return 1 - return 0 -} - -# args: -# remote_path - $1 -# disable_feed_credential - $2 -get_http_header_wget() { - eval $invocation - local remote_path="$1" - local disable_feed_credential="$2" - local wget_options="-q -S --spider --tries 5 " - - local wget_options_extra='' - - # Test for options that aren't supported on all wget implementations. - if [[ $(wget -h 2>&1 | grep -E 'waitretry|connect-timeout') ]]; then - wget_options_extra="--waitretry 2 --connect-timeout 15 " - else - say "wget extra options are unavailable for this environment" - fi - - remote_path_with_credential="$remote_path" - if [ "$disable_feed_credential" = false ]; then - remote_path_with_credential+="$feed_credential" - fi - - wget $wget_options $wget_options_extra "$remote_path_with_credential" 2>&1 - - return $? -} - -# args: -# remote_path - $1 -# [out_path] - $2 - stdout if not provided -download() { - eval $invocation - - local remote_path="$1" - local out_path="${2:-}" - - if [[ "$remote_path" != "http"* ]]; then - cp "$remote_path" "$out_path" - return $? - fi - - local failed=false - local attempts=0 - while [ $attempts -lt 3 ]; do - attempts=$((attempts+1)) - failed=false - if machine_has "curl"; then - downloadcurl "$remote_path" "$out_path" || failed=true - elif machine_has "wget"; then - downloadwget "$remote_path" "$out_path" || failed=true - else - say_err "Missing dependency: neither curl nor wget was found." - exit 1 - fi - - if [ "$failed" = false ] || [ $attempts -ge 3 ] || { [ ! -z $http_code ] && [ $http_code = "404" ]; }; then - break - fi - - say "Download attempt #$attempts has failed: $http_code $download_error_msg" - say "Attempt #$((attempts+1)) will start in $((attempts*10)) seconds." - sleep $((attempts*10)) - done - - if [ "$failed" = true ]; then - say_verbose "Download failed: $remote_path" - return 1 - fi - return 0 -} - -# Updates global variables $http_code and $download_error_msg -downloadcurl() { - eval $invocation - unset http_code - unset download_error_msg - local remote_path="$1" - local out_path="${2:-}" - # Append feed_credential as late as possible before calling curl to avoid logging feed_credential - # Avoid passing URI with credentials to functions: note, most of them echoing parameters of invocation in verbose output. - local remote_path_with_credential="${remote_path}${feed_credential}" - local curl_options="--retry 20 --retry-delay 2 --connect-timeout 15 -sSL -f --create-dirs " - local curl_exit_code=0; - if [ -z "$out_path" ]; then - curl_output=$(curl $curl_options "$remote_path_with_credential" 2>&1) - curl_exit_code=$? - echo "$curl_output" - else - curl_output=$(curl $curl_options -o "$out_path" "$remote_path_with_credential" 2>&1) - curl_exit_code=$? - fi - - # Regression in curl causes curl with --retry to return a 0 exit code even when it fails to download a file - https://github.com/curl/curl/issues/17554 - if [ $curl_exit_code -eq 0 ] && echo "$curl_output" | grep -q "^curl: ([0-9]*) "; then - curl_exit_code=$(echo "$curl_output" | sed 's/curl: (\([0-9]*\)).*/\1/') - fi - - if [ $curl_exit_code -gt 0 ]; then - download_error_msg="Unable to download $remote_path." - # Check for curl timeout codes - if [[ $curl_exit_code == 7 || $curl_exit_code == 28 ]]; then - download_error_msg+=" Failed to reach the server: connection timeout." - else - local disable_feed_credential=false - local response=$(get_http_header_curl $remote_path $disable_feed_credential) - http_code=$( echo "$response" | awk '/^HTTP/{print $2}' | tail -1 ) - if [[ ! -z $http_code && $http_code != 2* ]]; then - download_error_msg+=" Returned HTTP status code: $http_code." - fi - fi - say_verbose "$download_error_msg" - return 1 - fi - return 0 -} - - -# Updates global variables $http_code and $download_error_msg -downloadwget() { - eval $invocation - unset http_code - unset download_error_msg - local remote_path="$1" - local out_path="${2:-}" - # Append feed_credential as late as possible before calling wget to avoid logging feed_credential - local remote_path_with_credential="${remote_path}${feed_credential}" - local wget_options="--tries 20 " - - local wget_options_extra='' - local wget_result='' - - # Test for options that aren't supported on all wget implementations. - if [[ $(wget -h 2>&1 | grep -E 'waitretry|connect-timeout') ]]; then - wget_options_extra="--waitretry 2 --connect-timeout 15 " - else - say "wget extra options are unavailable for this environment" - fi - - if [ -z "$out_path" ]; then - wget -q $wget_options $wget_options_extra -O - "$remote_path_with_credential" 2>&1 - wget_result=$? - else - wget $wget_options $wget_options_extra -O "$out_path" "$remote_path_with_credential" 2>&1 - wget_result=$? - fi - - if [[ $wget_result != 0 ]]; then - local disable_feed_credential=false - local response=$(get_http_header_wget $remote_path $disable_feed_credential) - http_code=$( echo "$response" | awk '/^ HTTP/{print $2}' | tail -1 ) - download_error_msg="Unable to download $remote_path." - if [[ ! -z $http_code && $http_code != 2* ]]; then - download_error_msg+=" Returned HTTP status code: $http_code." - # wget exit code 4 stands for network-issue - elif [[ $wget_result == 4 ]]; then - download_error_msg+=" Failed to reach the server: connection timeout." - fi - say_verbose "$download_error_msg" - return 1 - fi - - return 0 -} - -get_download_link_from_aka_ms() { - eval $invocation - - #quality is not supported for LTS or STS channel - #STS maps to current - if [[ ! -z "$normalized_quality" && ("$normalized_channel" == "LTS" || "$normalized_channel" == "STS") ]]; then - normalized_quality="" - say_warning "Specifying quality for STS or LTS channel is not supported, the quality will be ignored." - fi - - say_verbose "Retrieving primary payload URL from aka.ms for channel: '$normalized_channel', quality: '$normalized_quality', product: '$normalized_product', os: '$normalized_os', architecture: '$normalized_architecture'." - - #construct aka.ms link - aka_ms_link="https://aka.ms/dotnet" - if [ "$internal" = true ]; then - aka_ms_link="$aka_ms_link/internal" - fi - aka_ms_link="$aka_ms_link/$normalized_channel" - if [[ ! -z "$normalized_quality" ]]; then - aka_ms_link="$aka_ms_link/$normalized_quality" - fi - aka_ms_link="$aka_ms_link/$normalized_product-$normalized_os-$normalized_architecture.tar.gz" - say_verbose "Constructed aka.ms link: '$aka_ms_link'." - - #get HTTP response - #do not pass credentials as a part of the $aka_ms_link and do not apply credentials in the get_http_header function - #otherwise the redirect link would have credentials as well - #it would result in applying credentials twice to the resulting link and thus breaking it, and in echoing credentials to the output as a part of redirect link - disable_feed_credential=true - response="$(get_http_header $aka_ms_link $disable_feed_credential)" - - say_verbose "Received response: $response" - # Get results of all the redirects. - http_codes=$( echo "$response" | awk '$1 ~ /^HTTP/ {print $2}' ) - # They all need to be 301, otherwise some links are broken (except for the last, which is not a redirect but 200 or 404). - broken_redirects=$( echo "$http_codes" | sed '$d' | grep -v '301' ) - # The response may end without final code 2xx/4xx/5xx somehow, e.g. network restrictions on www.bing.com causes redirecting to bing.com fails with connection refused. - # In this case it should not exclude the last. - last_http_code=$( echo "$http_codes" | tail -n 1 ) - if ! [[ $last_http_code =~ ^(2|4|5)[0-9][0-9]$ ]]; then - broken_redirects=$( echo "$http_codes" | grep -v '301' ) - fi - - # All HTTP codes are 301 (Moved Permanently), the redirect link exists. - if [[ -z "$broken_redirects" ]]; then - aka_ms_download_link=$( echo "$response" | awk '$1 ~ /^Location/{print $2}' | tail -1 | tr -d '\r') - - if [[ -z "$aka_ms_download_link" ]]; then - say_verbose "The aka.ms link '$aka_ms_link' is not valid: failed to get redirect location." - return 1 - fi - - say_verbose "The redirect location retrieved: '$aka_ms_download_link'." - return 0 - else - say_verbose "The aka.ms link '$aka_ms_link' is not valid: received HTTP code: $(echo "$broken_redirects" | paste -sd "," -)." - return 1 - fi -} - -get_feeds_to_use() -{ - feeds=( - "https://builds.dotnet.microsoft.com/dotnet" - "https://ci.dot.net/public" - ) - - if [[ -n "$azure_feed" ]]; then - feeds=("$azure_feed") - fi - - if [[ -n "$uncached_feed" ]]; then - feeds=("$uncached_feed") - fi -} - -# THIS FUNCTION MAY EXIT (if the determined version is already installed). -generate_download_links() { - - download_links=() - specific_versions=() - effective_versions=() - link_types=() - - # If generate_akams_links returns false, no fallback to old links. Just terminate. - # This function may also 'exit' (if the determined version is already installed). - generate_akams_links || return - - # Check other feeds only if we haven't been able to find an aka.ms link. - if [[ "${#download_links[@]}" -lt 1 ]]; then - for feed in ${feeds[@]} - do - # generate_regular_links may also 'exit' (if the determined version is already installed). - generate_regular_links $feed || return - done - fi - - if [[ "${#download_links[@]}" -eq 0 ]]; then - say_err "Failed to resolve the exact version number." - return 1 - fi - - say_verbose "Generated ${#download_links[@]} links." - for link_index in ${!download_links[@]} - do - say_verbose "Link $link_index: ${link_types[$link_index]}, ${effective_versions[$link_index]}, ${download_links[$link_index]}" - done -} - -# THIS FUNCTION MAY EXIT (if the determined version is already installed). -generate_akams_links() { - local valid_aka_ms_link=true; - - normalized_version="$(to_lowercase "$version")" - if [[ "$normalized_version" != "latest" ]] && [ -n "$normalized_quality" ]; then - say_err "Quality and Version options are not allowed to be specified simultaneously. See https://learn.microsoft.com/dotnet/core/tools/dotnet-install-script#options for details." - return 1 - fi - - if [[ -n "$json_file" || "$normalized_version" != "latest" ]]; then - # aka.ms links are not needed when exact version is specified via command or json file - return - fi - - get_download_link_from_aka_ms || valid_aka_ms_link=false - - if [[ "$valid_aka_ms_link" == true ]]; then - say_verbose "Retrieved primary payload URL from aka.ms link: '$aka_ms_download_link'." - say_verbose "Downloading using legacy url will not be attempted." - - download_link=$aka_ms_download_link - - #get version from the path - IFS='/' - read -ra pathElems <<< "$download_link" - count=${#pathElems[@]} - specific_version="${pathElems[count-2]}" - unset IFS; - say_verbose "Version: '$specific_version'." - - #Retrieve effective version - effective_version="$(get_specific_product_version "$azure_feed" "$specific_version" "$download_link")" - - # Add link info to arrays - download_links+=($download_link) - specific_versions+=($specific_version) - effective_versions+=($effective_version) - link_types+=("aka.ms") - - # Check if the SDK version is already installed. - if [[ "$dry_run" != true ]] && is_dotnet_package_installed "$install_root" "$asset_relative_path" "$effective_version"; then - say "$asset_name with version '$effective_version' is already installed." - exit 0 - fi - - return 0 - fi - - # if quality is specified - exit with error - there is no fallback approach - if [ ! -z "$normalized_quality" ]; then - say_err "Failed to locate the latest version in the channel '$normalized_channel' with '$normalized_quality' quality for '$normalized_product', os: '$normalized_os', architecture: '$normalized_architecture'." - say_err "Refer to: https://aka.ms/dotnet-os-lifecycle for information on .NET Core support." - return 1 - fi - say_verbose "Falling back to latest.version file approach." -} - -# THIS FUNCTION MAY EXIT (if the determined version is already installed) -# args: -# feed - $1 -generate_regular_links() { - local feed="$1" - local valid_legacy_download_link=true - - specific_version=$(get_specific_version_from_version "$feed" "$channel" "$normalized_architecture" "$version" "$json_file") || specific_version='0' - - if [[ "$specific_version" == '0' ]]; then - say_verbose "Failed to resolve the specific version number using feed '$feed'" - return - fi - - effective_version="$(get_specific_product_version "$feed" "$specific_version")" - say_verbose "specific_version=$specific_version" - - download_link="$(construct_download_link "$feed" "$channel" "$normalized_architecture" "$specific_version" "$normalized_os")" - say_verbose "Constructed primary named payload URL: $download_link" - - # Add link info to arrays - download_links+=($download_link) - specific_versions+=($specific_version) - effective_versions+=($effective_version) - link_types+=("primary") - - legacy_download_link="$(construct_legacy_download_link "$feed" "$channel" "$normalized_architecture" "$specific_version")" || valid_legacy_download_link=false - - if [ "$valid_legacy_download_link" = true ]; then - say_verbose "Constructed legacy named payload URL: $legacy_download_link" - - download_links+=($legacy_download_link) - specific_versions+=($specific_version) - effective_versions+=($effective_version) - link_types+=("legacy") - else - legacy_download_link="" - say_verbose "Could not construct a legacy_download_link; omitting..." - fi - - # Check if the SDK version is already installed. - if [[ "$dry_run" != true ]] && is_dotnet_package_installed "$install_root" "$asset_relative_path" "$effective_version"; then - say "$asset_name with version '$effective_version' is already installed." - exit 0 - fi -} - -print_dry_run() { - - say "Payload URLs:" - - for link_index in "${!download_links[@]}" - do - say "URL #$link_index - ${link_types[$link_index]}: ${download_links[$link_index]}" - done - - resolved_version=${specific_versions[0]} - repeatable_command="./$script_name --version "\""$resolved_version"\"" --install-dir "\""$install_root"\"" --architecture "\""$normalized_architecture"\"" --os "\""$normalized_os"\""" - - if [ ! -z "$normalized_quality" ]; then - repeatable_command+=" --quality "\""$normalized_quality"\""" - fi - - if [[ "$runtime" == "dotnet" ]]; then - repeatable_command+=" --runtime "\""dotnet"\""" - elif [[ "$runtime" == "aspnetcore" ]]; then - repeatable_command+=" --runtime "\""aspnetcore"\""" - fi - - repeatable_command+="$non_dynamic_parameters" - - if [ -n "$feed_credential" ]; then - repeatable_command+=" --feed-credential "\"""\""" - fi - - say "Repeatable invocation: $repeatable_command" -} - -calculate_vars() { - eval $invocation - - script_name=$(basename "$0") - normalized_architecture="$(get_normalized_architecture_from_architecture "$architecture")" - say_verbose "Normalized architecture: '$normalized_architecture'." - normalized_os="$(get_normalized_os "$user_defined_os")" - say_verbose "Normalized OS: '$normalized_os'." - normalized_quality="$(get_normalized_quality "$quality")" - say_verbose "Normalized quality: '$normalized_quality'." - normalized_channel="$(get_normalized_channel "$channel")" - say_verbose "Normalized channel: '$normalized_channel'." - normalized_product="$(get_normalized_product "$runtime")" - say_verbose "Normalized product: '$normalized_product'." - install_root="$(resolve_installation_path "$install_dir")" - say_verbose "InstallRoot: '$install_root'." - - normalized_architecture="$(get_normalized_architecture_for_specific_sdk_version "$version" "$normalized_channel" "$normalized_architecture")" - - if [[ "$runtime" == "dotnet" ]]; then - asset_relative_path="shared/Microsoft.NETCore.App" - asset_name=".NET Core Runtime" - elif [[ "$runtime" == "aspnetcore" ]]; then - asset_relative_path="shared/Microsoft.AspNetCore.App" - asset_name="ASP.NET Core Runtime" - elif [ -z "$runtime" ]; then - asset_relative_path="sdk" - asset_name=".NET Core SDK" - fi - - get_feeds_to_use -} - -install_dotnet() { - eval $invocation - local download_failed=false - local download_completed=false - local remote_file_size=0 - - mkdir -p "$install_root" - zip_path="${zip_path:-$(mktemp "$temporary_file_template")}" - say_verbose "Archive path: $zip_path" - - for link_index in "${!download_links[@]}" - do - download_link="${download_links[$link_index]}" - specific_version="${specific_versions[$link_index]}" - effective_version="${effective_versions[$link_index]}" - link_type="${link_types[$link_index]}" - - say "Attempting to download using $link_type link $download_link" - - # The download function will set variables $http_code and $download_error_msg in case of failure. - download_failed=false - download "$download_link" "$zip_path" 2>&1 || download_failed=true - - if [ "$download_failed" = true ]; then - case $http_code in - 404) - say "The resource at $link_type link '$download_link' is not available." - ;; - *) - say "Failed to download $link_type link '$download_link': $http_code $download_error_msg" - ;; - esac - rm -f "$zip_path" 2>&1 && say_verbose "Temporary archive file $zip_path was removed" - else - download_completed=true - break - fi - done - - if [[ "$download_completed" == false ]]; then - say_err "Could not find \`$asset_name\` with version = $specific_version" - say_err "Refer to: https://aka.ms/dotnet-os-lifecycle for information on .NET Core support" - return 1 - fi - - remote_file_size="$(get_remote_file_size "$download_link")" - - say "Extracting archive from $download_link" - extract_dotnet_package "$zip_path" "$install_root" "$remote_file_size" || return 1 - - # Check if the SDK version is installed; if not, fail the installation. - # if the version contains "RTM" or "servicing"; check if a 'release-type' SDK version is installed. - if [[ $specific_version == *"rtm"* || $specific_version == *"servicing"* ]]; then - IFS='-' - read -ra verArr <<< "$specific_version" - release_version="${verArr[0]}" - unset IFS; - say_verbose "Checking installation: version = $release_version" - if is_dotnet_package_installed "$install_root" "$asset_relative_path" "$release_version"; then - say "Installed version is $effective_version" - return 0 - fi - fi - - # Check if the standard SDK version is installed. - say_verbose "Checking installation: version = $effective_version" - if is_dotnet_package_installed "$install_root" "$asset_relative_path" "$effective_version"; then - say "Installed version is $effective_version" - return 0 - fi - - # Version verification failed. More likely something is wrong either with the downloaded content or with the verification algorithm. - say_err "Failed to verify the version of installed \`$asset_name\`.\nInstallation source: $download_link.\nInstallation location: $install_root.\nReport the bug at https://github.com/dotnet/install-scripts/issues." - say_err "\`$asset_name\` with version = $effective_version failed to install with an error." - return 1 -} - -args=("$@") - -local_version_file_relative_path="/.version" -bin_folder_relative_path="" -temporary_file_template="${TMPDIR:-/tmp}/dotnet.XXXXXXXXX" - -channel="LTS" -version="Latest" -json_file="" -install_dir="" -architecture="" -dry_run=false -no_path=false -azure_feed="" -uncached_feed="" -feed_credential="" -verbose=false -runtime="" -runtime_id="" -quality="" -internal=false -override_non_versioned_files=true -non_dynamic_parameters="" -user_defined_os="" - -while [ $# -ne 0 ] -do - name="$1" - case "$name" in - -c|--channel|-[Cc]hannel) - shift - channel="$1" - ;; - -v|--version|-[Vv]ersion) - shift - version="$1" - ;; - -q|--quality|-[Qq]uality) - shift - quality="$1" - ;; - --internal|-[Ii]nternal) - internal=true - non_dynamic_parameters+=" $name" - ;; - -i|--install-dir|-[Ii]nstall[Dd]ir) - shift - install_dir="$1" - ;; - --arch|--architecture|-[Aa]rch|-[Aa]rchitecture) - shift - architecture="$1" - ;; - --os|-[Oo][SS]) - shift - user_defined_os="$1" - ;; - --shared-runtime|-[Ss]hared[Rr]untime) - say_warning "The --shared-runtime flag is obsolete and may be removed in a future version of this script. The recommended usage is to specify '--runtime dotnet'." - if [ -z "$runtime" ]; then - runtime="dotnet" - fi - ;; - --runtime|-[Rr]untime) - shift - runtime="$1" - if [[ "$runtime" != "dotnet" ]] && [[ "$runtime" != "aspnetcore" ]]; then - say_err "Unsupported value for --runtime: '$1'. Valid values are 'dotnet' and 'aspnetcore'." - if [[ "$runtime" == "windowsdesktop" ]]; then - say_err "WindowsDesktop archives are manufactured for Windows platforms only." - fi - exit 1 - fi - ;; - --dry-run|-[Dd]ry[Rr]un) - dry_run=true - ;; - --no-path|-[Nn]o[Pp]ath) - no_path=true - non_dynamic_parameters+=" $name" - ;; - --verbose|-[Vv]erbose) - verbose=true - non_dynamic_parameters+=" $name" - ;; - --azure-feed|-[Aa]zure[Ff]eed) - shift - azure_feed="$1" - non_dynamic_parameters+=" $name "\""$1"\""" - ;; - --uncached-feed|-[Uu]ncached[Ff]eed) - shift - uncached_feed="$1" - non_dynamic_parameters+=" $name "\""$1"\""" - ;; - --feed-credential|-[Ff]eed[Cc]redential) - shift - feed_credential="$1" - #feed_credential should start with "?", for it to be added to the end of the link. - #adding "?" at the beginning of the feed_credential if needed. - [[ -z "$(echo $feed_credential)" ]] || [[ $feed_credential == \?* ]] || feed_credential="?$feed_credential" - ;; - --runtime-id|-[Rr]untime[Ii]d) - shift - runtime_id="$1" - non_dynamic_parameters+=" $name "\""$1"\""" - say_warning "Use of --runtime-id is obsolete and should be limited to the versions below 2.1. To override architecture, use --architecture option instead. To override OS, use --os option instead." - ;; - --jsonfile|-[Jj][Ss]on[Ff]ile) - shift - json_file="$1" - ;; - --skip-non-versioned-files|-[Ss]kip[Nn]on[Vv]ersioned[Ff]iles) - override_non_versioned_files=false - non_dynamic_parameters+=" $name" - ;; - --keep-zip|-[Kk]eep[Zz]ip) - keep_zip=true - non_dynamic_parameters+=" $name" - ;; - --zip-path|-[Zz]ip[Pp]ath) - shift - zip_path="$1" - ;; - -?|--?|-h|--help|-[Hh]elp) - script_name="dotnet-install.sh" - echo ".NET Tools Installer" - echo "Usage:" - echo " # Install a .NET SDK of a given Quality from a given Channel" - echo " $script_name [-c|--channel ] [-q|--quality ]" - echo " # Install a .NET SDK of a specific public version" - echo " $script_name [-v|--version ]" - echo " $script_name -h|-?|--help" - echo "" - echo "$script_name is a simple command line interface for obtaining dotnet cli." - echo " Note that the intended use of this script is for Continuous Integration (CI) scenarios, where:" - echo " - The SDK needs to be installed without user interaction and without admin rights." - echo " - The SDK installation doesn't need to persist across multiple CI runs." - echo " To set up a development environment or to run apps, use installers rather than this script. Visit https://dotnet.microsoft.com/download to get the installer." - echo "" - echo "Options:" - echo " -c,--channel Download from the channel specified, Defaults to \`$channel\`." - echo " -Channel" - echo " Possible values:" - echo " - STS - the most recent Standard Term Support release" - echo " - LTS - the most recent Long Term Support release" - echo " - 2-part version in a format A.B - represents a specific release" - echo " examples: 2.0; 1.0" - echo " - 3-part version in a format A.B.Cxx - represents a specific SDK release" - echo " examples: 5.0.1xx, 5.0.2xx." - echo " Supported since 5.0 release" - echo " Warning: Value 'Current' is deprecated for the Channel parameter. Use 'STS' instead." - echo " Note: The version parameter overrides the channel parameter when any version other than 'latest' is used." - echo " -v,--version Use specific VERSION, Defaults to \`$version\`." - echo " -Version" - echo " Possible values:" - echo " - latest - the latest build on specific channel" - echo " - 3-part version in a format A.B.C - represents specific version of build" - echo " examples: 2.0.0-preview2-006120; 1.1.0" - echo " -q,--quality Download the latest build of specified quality in the channel." - echo " -Quality" - echo " The possible values are: daily, preview, GA." - echo " Works only in combination with channel. Not applicable for STS and LTS channels and will be ignored if those channels are used." - echo " For SDK use channel in A.B.Cxx format. Using quality for SDK together with channel in A.B format is not supported." - echo " Supported since 5.0 release." - echo " Note: The version parameter overrides the channel parameter when any version other than 'latest' is used, and therefore overrides the quality." - echo " --internal,-Internal Download internal builds. Requires providing credentials via --feed-credential parameter." - echo " --feed-credential Token to access Azure feed. Used as a query string to append to the Azure feed." - echo " -FeedCredential This parameter typically is not specified." - echo " -i,--install-dir Install under specified location (see Install Location below)" - echo " -InstallDir" - echo " --architecture Architecture of dotnet binaries to be installed, Defaults to \`$architecture\`." - echo " --arch,-Architecture,-Arch" - echo " Possible values: x64, arm, arm64, s390x, ppc64le and loongarch64" - echo " --os Specifies operating system to be used when selecting the installer." - echo " Overrides the OS determination approach used by the script. Supported values: osx, linux, linux-musl, freebsd, rhel.6." - echo " In case any other value is provided, the platform will be determined by the script based on machine configuration." - echo " Not supported for legacy links. Use --runtime-id to specify platform for legacy links." - echo " Refer to: https://aka.ms/dotnet-os-lifecycle for more information." - echo " --runtime Installs a shared runtime only, without the SDK." - echo " -Runtime" - echo " Possible values:" - echo " - dotnet - the Microsoft.NETCore.App shared runtime" - echo " - aspnetcore - the Microsoft.AspNetCore.App shared runtime" - echo " --dry-run,-DryRun Do not perform installation. Display download link." - echo " --no-path, -NoPath Do not set PATH for the current process." - echo " --verbose,-Verbose Display diagnostics information." - echo " --azure-feed,-AzureFeed For internal use only." - echo " Allows using a different storage to download SDK archives from." - echo " --uncached-feed,-UncachedFeed For internal use only." - echo " Allows using a different storage to download SDK archives from." - echo " --skip-non-versioned-files Skips non-versioned files if they already exist, such as the dotnet executable." - echo " -SkipNonVersionedFiles" - echo " --jsonfile Determines the SDK version from a user specified global.json file." - echo " Note: global.json must have a value for 'SDK:Version'" - echo " --keep-zip,-KeepZip If set, downloaded file is kept." - echo " --zip-path, -ZipPath If set, downloaded file is stored at the specified path." - echo " -?,--?,-h,--help,-Help Shows this help message" - echo "" - echo "Install Location:" - echo " Location is chosen in following order:" - echo " - --install-dir option" - echo " - Environmental variable DOTNET_INSTALL_DIR" - echo " - $HOME/.dotnet" - exit 0 - ;; - *) - say_err "Unknown argument \`$name\`" - exit 1 - ;; - esac - - shift -done - -say_verbose "Note that the intended use of this script is for Continuous Integration (CI) scenarios, where:" -say_verbose "- The SDK needs to be installed without user interaction and without admin rights." -say_verbose "- The SDK installation doesn't need to persist across multiple CI runs." -say_verbose "To set up a development environment or to run apps, use installers rather than this script. Visit https://dotnet.microsoft.com/download to get the installer.\n" - -if [ "$internal" = true ] && [ -z "$(echo $feed_credential)" ]; then - message="Provide credentials via --feed-credential parameter." - if [ "$dry_run" = true ]; then - say_warning "$message" - else - say_err "$message" - exit 1 - fi -fi - -check_min_reqs -calculate_vars -# generate_regular_links call below will 'exit' if the determined version is already installed. -generate_download_links - -if [[ "$dry_run" = true ]]; then - print_dry_run - exit 0 -fi - -install_dotnet - -bin_path="$(get_absolute_path "$(combine_paths "$install_root" "$bin_folder_relative_path")")" -if [ "$no_path" = false ]; then - say "Adding to current process PATH: \`$bin_path\`. Note: This change will be visible only when sourcing script." - export PATH="$bin_path":"$PATH" -else - say "Binaries of dotnet can be found in $bin_path" -fi - -say "Note that the script does not resolve dependencies during installation." -say "To check the list of dependencies, go to https://learn.microsoft.com/dotnet/core/install, select your operating system and check the \"Dependencies\" section." -say "Installation finished successfully." From 71a09363898815131830228266baf9b1330a50fb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 22 Aug 2025 20:52:48 +0000 Subject: [PATCH 05/10] Remove dotnet installation instructions and focus on important project information Co-authored-by: KSemenenko <4385716+KSemenenko@users.noreply.github.com> --- .github/copilot-instructions.md | 43 +++++++++++---------------------- 1 file changed, 14 insertions(+), 29 deletions(-) diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 88f9311..904f4dd 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -1,37 +1,13 @@ # ManagedCode.Communication -A high-performance .NET 9.0 library providing Result types and railway-oriented programming patterns for robust error handling in distributed applications. +Result pattern for .NET that replaces exceptions with type-safe return values. Features railway-oriented programming, ASP.NET Core integration, RFC 7807 Problem Details, and built-in pagination. -## Project Requirements +## Build Commands -This project requires **.NET 9.0 SDK**. Install it using: - -```bash -dotnet --list-sdks # Check if 9.0.x is available -``` - -If not available, install .NET 9.0: -```bash -wget -q https://dot.net/v1/dotnet-install.sh && chmod +x dotnet-install.sh -./dotnet-install.sh --version 9.0.100 --install-dir ~/.dotnet -export PATH="~/.dotnet:$PATH" -``` - -## Build and Test - -**Restore packages:** ```bash dotnet restore -``` - -**Build all projects:** -```bash -dotnet build --configuration Release -``` - -**Run tests:** -```bash -dotnet test --configuration Release --no-build +dotnet build +dotnet test ``` ## Project Structure @@ -44,7 +20,16 @@ dotnet test --configuration Release --no-build ## Key Concepts -This library implements railway-oriented programming patterns using Result types for error handling without exceptions: +This library implements the Result pattern for functional error handling without exceptions: + +**Core Result Types:** +- `Result` - Success/failure without a value +- `Result` - Success with value T or failure +- `CollectionResult` - Collections with built-in pagination +- `Problem` - RFC 7807 compliant error details + +**Railway-Oriented Programming:** +Chain operations using functional combinators like `Map`, `Bind`/`Then`, `Tap`/`Do`, `Match`, and `Compensate`. ```csharp // Basic Result usage From 4f4f21a1702a08ca615734c9d4bd89953231fbfa Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 22 Aug 2025 20:58:32 +0000 Subject: [PATCH 06/10] Add comprehensive best practices, code style, and Copilot instructions Co-authored-by: KSemenenko <4385716+KSemenenko@users.noreply.github.com> --- .github/copilot-instructions.md | 180 ++++++++++++++++++++++++++++++++ 1 file changed, 180 insertions(+) diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 904f4dd..f8f71b0 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -47,4 +47,184 @@ if (result.IsSuccess) var chainResult = ValidateEmail("test@example.com") .Then(email => ProcessEmail(email)) .Then(processed => SaveToDatabase(processed)); +``` + +## Code Style & Conventions + +**Framework Configuration:** +- Target: .NET 9.0 +- Nullable reference types enabled +- TreatWarningsAsErrors = true +- EnableNETAnalyzers = true + +**Formatting (from .editorconfig):** +- 4 spaces for C# indentation +- CRLF line endings +- Opening braces on new lines (`csharp_new_line_before_open_brace = all`) +- Spaces around binary operators +- No space after cast: `(int)value` + +**C# Style Preferences:** +- Use `var` only when type is apparent: `var user = GetUser();` +- Prefer explicit types for built-ins: `int count = 0;` not `var count = 0;` +- Expression-bodied properties preferred: `public string Name => _name;` +- Pattern matching over is/as: `if (obj is User user)` +- Null conditional operators: `user?.Name` over null checks + +**Naming Conventions:** +- PascalCase for public members, types, constants +- No prefixes for interfaces, fields, or private members +- Method names should be descriptive: `ValidateEmailAddress()` not `Validate()` + +## Testing Patterns + +**Test Framework:** +- xUnit for test framework +- FluentAssertions for readable assertions +- Coverlet for code coverage + +**Test Structure:** +```csharp +[Fact] +public void Method_Scenario_ExpectedResult() +{ + // Arrange + var input = CreateTestData(); + + // Act + var result = systemUnderTest.Method(input); + + // Assert + result.IsSuccess.Should().BeTrue(); + result.Value.Should().NotBeNull(); +} +``` + +## Best Practices + +**DO ✅ - Result Pattern Usage:** +```csharp +// Use Result for operations that can fail +public Result GetUser(int id) +{ + var user = _repository.FindById(id); + return user != null + ? Result.Succeed(user) + : Result.FailNotFound($"User {id} not found"); +} + +// Chain operations using railway-oriented programming +public Result ProcessOrder(OrderDto dto) +{ + return ValidateOrder(dto) + .Then(CreateOrder) + .Then(CalculateTotals) + .Then(SaveOrder); +} + +// Provide specific error information +public Result ValidateEmail(string email) +{ + if (string.IsNullOrEmpty(email)) + return Result.FailValidation(("email", "Email is required")); + + if (!email.Contains("@")) + return Result.FailValidation(("email", "Invalid email format")); + + return Result.Succeed(); +} + +// Use CollectionResult for paginated data +public CollectionResult GetProducts(int page, int pageSize) +{ + var products = _repository.GetPaged(page, pageSize); + var total = _repository.Count(); + return CollectionResult.Succeed(products, page, pageSize, total); +} +``` + +**DON'T ❌ - Anti-patterns:** +```csharp +// DON'T: Throw exceptions from Result-returning methods +public Result GetUser(int id) +{ + if (id <= 0) + return Result.FailValidation(("id", "ID must be positive")); // ✅ + // NOT: throw new ArgumentException("Invalid ID"); // ❌ +} + +// DON'T: Ignore Result values +var result = UpdateUser(user); +if (result.IsFailed) + return result; // ✅ Handle the failure + +// DON'T: Mix Result and exceptions +// DON'T: Create generic error messages - be specific +return Result.Fail("User creation failed", "Email already exists"); // ✅ +``` + +**Performance Guidelines:** +1. `Result` and `Result` are structs - avoid boxing +2. Use railway-oriented programming to avoid intermediate variables +3. Cache common Problem instances for frequent errors +4. Use `ConfigureAwait(false)` in library code + +## Framework Integration + +**ASP.NET Core:** +- Controllers can return Result types directly +- Automatic HTTP status code mapping from Problem Details +- Built-in filters for Result handling + +**Orleans:** +- Use `UseOrleansCommunication()` for automatic serialization +- Result types work across grain boundaries +- Problem Details preserved in distributed calls + +**Command Pattern:** +- Built-in command infrastructure with idempotency +- Commands implement `ICommand` or `ICommand` +- Automatic validation and result wrapping + +## Common Patterns + +**Validation:** +```csharp +private Result ValidateDto(CreateUserDto dto) +{ + var errors = new List<(string field, string message)>(); + + if (string.IsNullOrWhiteSpace(dto.Email)) + errors.Add(("email", "Email is required")); + + return errors.Any() + ? Result.FailValidation(errors.ToArray()) + : Result.Succeed(); +} +``` + +**Error Recovery:** +```csharp +public async Task> GetUserWithFallback(int id) +{ + return await GetUser(id) + .CompensateAsync(async error => + { + var archived = await GetArchivedUser(id); + return archived ?? Result.FailNotFound($"User {id} not found"); + }); +} +``` + +**Aggregating Results:** +```csharp +public Result CreateOrder(List items) +{ + var validationResults = items.Select(ValidateItem); + var combinedResult = Result.Combine(validationResults); + + return combinedResult.IsSuccess + ? ProcessOrder(items) + : Result.Fail(combinedResult.Problem); +} ``` \ No newline at end of file From 5684ef432051b313fde82b59612e4ee4ed69d16f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 22 Aug 2025 21:12:07 +0000 Subject: [PATCH 07/10] Improve copilot-instructions.md based on ASP.NET Core best practices Co-authored-by: KSemenenko <4385716+KSemenenko@users.noreply.github.com> --- .github/copilot-instructions.md | 246 ++++++++++---------------------- 1 file changed, 79 insertions(+), 167 deletions(-) diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index f8f71b0..5be3bf9 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -2,229 +2,141 @@ Result pattern for .NET that replaces exceptions with type-safe return values. Features railway-oriented programming, ASP.NET Core integration, RFC 7807 Problem Details, and built-in pagination. -## Build Commands +## General -```bash -dotnet restore -dotnet build -dotnet test -``` +* Always prefer Result types over throwing exceptions for expected error cases. +* Use railway-oriented programming patterns with `Then`, `Map`, `Compensate` for chaining operations. +* Always use the latest C# features, currently C# 13. +* Make only high confidence suggestions when reviewing code changes. +* Never change `global.json`, `Directory.Build.props`, or solution files unless explicitly asked. -## Project Structure +## .NET Environment -- **ManagedCode.Communication** - Core Result types and railway-oriented programming extensions -- **ManagedCode.Communication.AspNetCore** - ASP.NET Core integration, filters, and middleware -- **ManagedCode.Communication.Orleans** - Microsoft Orleans serialization support -- **ManagedCode.Communication.Tests** - Test suite -- **ManagedCode.Communication.Benchmark** - Performance benchmarks +* This project targets .NET 9.0 and uses C# 13.0. +* Ensure you have .NET 9.0 SDK installed to build and run the project. +* The project uses nullable reference types and treats warnings as errors. -## Key Concepts +## Building and Testing -This library implements the Result pattern for functional error handling without exceptions: +```bash +# Requires .NET 9.0 SDK +dotnet build ManagedCode.Communication.Tests/Tests.ManagedCode.Communication.sln +dotnet test ManagedCode.Communication.Tests/Tests.ManagedCode.Communication.sln +``` -**Core Result Types:** -- `Result` - Success/failure without a value -- `Result` - Success with value T or failure -- `CollectionResult` - Collections with built-in pagination -- `Problem` - RFC 7807 compliant error details +## Formatting -**Railway-Oriented Programming:** -Chain operations using functional combinators like `Map`, `Bind`/`Then`, `Tap`/`Do`, `Match`, and `Compensate`. +* Apply code-formatting style defined in `.editorconfig`. +* Prefer file-scoped namespace declarations and single-line using directives. +* Insert a newline before the opening curly brace of any code block. +* Use pattern matching and switch expressions wherever possible. +* Use `nameof` instead of string literals when referring to member names. -```csharp -// Basic Result usage -var success = Result.Succeed(); -var failure = Result.Fail("Error message"); +### Nullable Reference Types -// Generic Result with value -var result = Result.Succeed("Hello World"); -if (result.IsSuccess) -{ - Console.WriteLine(result.Value); -} +* Declare variables non-nullable, and check for `null` at entry points. +* Always use `is null` or `is not null` instead of `== null` or `!= null`. +* Trust the C# null annotations and don't add null checks when the type system says a value cannot be null. -// Railway-oriented programming -var chainResult = ValidateEmail("test@example.com") - .Then(email => ProcessEmail(email)) - .Then(processed => SaveToDatabase(processed)); -``` +## Testing + +* We use xUnit for tests with FluentAssertions for assertions. +* Do not emit "Arrange", "Act" or "Assert" comments. +* Use the naming pattern: `Method_Scenario_ExpectedResult()`. +* Copy existing style in nearby test files for method names and capitalization. -## Code Style & Conventions - -**Framework Configuration:** -- Target: .NET 9.0 -- Nullable reference types enabled -- TreatWarningsAsErrors = true -- EnableNETAnalyzers = true - -**Formatting (from .editorconfig):** -- 4 spaces for C# indentation -- CRLF line endings -- Opening braces on new lines (`csharp_new_line_before_open_brace = all`) -- Spaces around binary operators -- No space after cast: `(int)value` - -**C# Style Preferences:** -- Use `var` only when type is apparent: `var user = GetUser();` -- Prefer explicit types for built-ins: `int count = 0;` not `var count = 0;` -- Expression-bodied properties preferred: `public string Name => _name;` -- Pattern matching over is/as: `if (obj is User user)` -- Null conditional operators: `user?.Name` over null checks - -**Naming Conventions:** -- PascalCase for public members, types, constants -- No prefixes for interfaces, fields, or private members -- Method names should be descriptive: `ValidateEmailAddress()` not `Validate()` - -## Testing Patterns - -**Test Framework:** -- xUnit for test framework -- FluentAssertions for readable assertions -- Coverlet for code coverage - -**Test Structure:** ```csharp [Fact] -public void Method_Scenario_ExpectedResult() +public void Succeed_ShouldCreateSuccessfulResult() { - // Arrange - var input = CreateTestData(); - - // Act - var result = systemUnderTest.Method(input); + var result = Result.Succeed(); - // Assert result.IsSuccess.Should().BeTrue(); - result.Value.Should().NotBeNull(); + result.IsFailed.Should().BeFalse(); + result.Problem.Should().BeNull(); } ``` +## Core Result Types + +This library implements the Result pattern for functional error handling: + +* `Result` - Success/failure without a value +* `Result` - Success with value T or failure +* `CollectionResult` - Collections with built-in pagination +* `Problem` - RFC 7807 compliant error details + +## Railway-Oriented Programming + +Chain operations using functional combinators: + +```csharp +// Basic Result usage +var success = Result.Succeed(); +var failure = Result.Fail("Error message"); + +// Railway-oriented programming +return ValidateEmail(email) + .Then(ProcessEmail) + .Then(SaveToDatabase); +``` + ## Best Practices -**DO ✅ - Result Pattern Usage:** +**DO ✅ Use Result for operations that can fail:** ```csharp -// Use Result for operations that can fail public Result GetUser(int id) { var user = _repository.FindById(id); - return user != null + return user is not null ? Result.Succeed(user) : Result.FailNotFound($"User {id} not found"); } +``` -// Chain operations using railway-oriented programming -public Result ProcessOrder(OrderDto dto) -{ - return ValidateOrder(dto) - .Then(CreateOrder) - .Then(CalculateTotals) - .Then(SaveOrder); -} - -// Provide specific error information +**DO ✅ Provide specific error information:** +```csharp public Result ValidateEmail(string email) { if (string.IsNullOrEmpty(email)) return Result.FailValidation(("email", "Email is required")); - if (!email.Contains("@")) + if (!email.Contains('@')) return Result.FailValidation(("email", "Invalid email format")); return Result.Succeed(); } - -// Use CollectionResult for paginated data -public CollectionResult GetProducts(int page, int pageSize) -{ - var products = _repository.GetPaged(page, pageSize); - var total = _repository.Count(); - return CollectionResult.Succeed(products, page, pageSize, total); -} ``` -**DON'T ❌ - Anti-patterns:** +**DON'T ❌ Throw exceptions from Result-returning methods:** ```csharp -// DON'T: Throw exceptions from Result-returning methods public Result GetUser(int id) { if (id <= 0) return Result.FailValidation(("id", "ID must be positive")); // ✅ // NOT: throw new ArgumentException("Invalid ID"); // ❌ } +``` -// DON'T: Ignore Result values +**DON'T ❌ Ignore Result values:** +```csharp var result = UpdateUser(user); if (result.IsFailed) return result; // ✅ Handle the failure - -// DON'T: Mix Result and exceptions -// DON'T: Create generic error messages - be specific -return Result.Fail("User creation failed", "Email already exists"); // ✅ ``` -**Performance Guidelines:** -1. `Result` and `Result` are structs - avoid boxing -2. Use railway-oriented programming to avoid intermediate variables -3. Cache common Problem instances for frequent errors -4. Use `ConfigureAwait(false)` in library code - ## Framework Integration **ASP.NET Core:** -- Controllers can return Result types directly -- Automatic HTTP status code mapping from Problem Details -- Built-in filters for Result handling +* Controllers can return Result types directly +* Automatic HTTP status code mapping from Problem Details +* Built-in filters for Result handling **Orleans:** -- Use `UseOrleansCommunication()` for automatic serialization -- Result types work across grain boundaries -- Problem Details preserved in distributed calls - -**Command Pattern:** -- Built-in command infrastructure with idempotency -- Commands implement `ICommand` or `ICommand` -- Automatic validation and result wrapping - -## Common Patterns - -**Validation:** -```csharp -private Result ValidateDto(CreateUserDto dto) -{ - var errors = new List<(string field, string message)>(); - - if (string.IsNullOrWhiteSpace(dto.Email)) - errors.Add(("email", "Email is required")); - - return errors.Any() - ? Result.FailValidation(errors.ToArray()) - : Result.Succeed(); -} -``` - -**Error Recovery:** -```csharp -public async Task> GetUserWithFallback(int id) -{ - return await GetUser(id) - .CompensateAsync(async error => - { - var archived = await GetArchivedUser(id); - return archived ?? Result.FailNotFound($"User {id} not found"); - }); -} -``` +* Use `UseOrleansCommunication()` for automatic serialization +* Result types work across grain boundaries -**Aggregating Results:** -```csharp -public Result CreateOrder(List items) -{ - var validationResults = items.Select(ValidateItem); - var combinedResult = Result.Combine(validationResults); - - return combinedResult.IsSuccess - ? ProcessOrder(items) - : Result.Fail(combinedResult.Problem); -} -``` \ No newline at end of file +**Performance:** +* `Result` and `Result` are structs - avoid boxing +* Use railway-oriented programming to avoid intermediate variables +* Cache common Problem instances for frequent errors \ No newline at end of file From b399fb23bdc51f183e56886765dcce2b1f4ab345 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 22 Aug 2025 21:25:49 +0000 Subject: [PATCH 08/10] Simplify build commands to basic dotnet build and dotnet test Co-authored-by: KSemenenko <4385716+KSemenenko@users.noreply.github.com> --- .github/copilot-instructions.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 5be3bf9..ba5cd5a 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -19,9 +19,8 @@ Result pattern for .NET that replaces exceptions with type-safe return values. F ## Building and Testing ```bash -# Requires .NET 9.0 SDK -dotnet build ManagedCode.Communication.Tests/Tests.ManagedCode.Communication.sln -dotnet test ManagedCode.Communication.Tests/Tests.ManagedCode.Communication.sln +dotnet build +dotnet test ``` ## Formatting From 4d0c5d399ac9d9661ec7a49e8533ce096385d03f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 22 Aug 2025 21:31:16 +0000 Subject: [PATCH 09/10] Improve copilot-instructions.md for better project consistency Co-authored-by: KSemenenko <4385716+KSemenenko@users.noreply.github.com> --- .github/copilot-instructions.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index ba5cd5a..924ddb0 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -5,15 +5,14 @@ Result pattern for .NET that replaces exceptions with type-safe return values. F ## General * Always prefer Result types over throwing exceptions for expected error cases. -* Use railway-oriented programming patterns with `Then`, `Map`, `Compensate` for chaining operations. +* Use railway-oriented programming patterns with `Map`, `Then`, `Compensate` for chaining operations. * Always use the latest C# features, currently C# 13. * Make only high confidence suggestions when reviewing code changes. -* Never change `global.json`, `Directory.Build.props`, or solution files unless explicitly asked. +* Never change `Directory.Build.props`, or solution files unless explicitly asked. ## .NET Environment * This project targets .NET 9.0 and uses C# 13.0. -* Ensure you have .NET 9.0 SDK installed to build and run the project. * The project uses nullable reference types and treats warnings as errors. ## Building and Testing From 052ecbe64d2a7a3b012a234b71134d08ba1920c0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 23 Aug 2025 09:05:15 +0000 Subject: [PATCH 10/10] Fix test example to match actual project code style Co-authored-by: KSemenenko <4385716+KSemenenko@users.noreply.github.com> --- .github/copilot-instructions.md | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 924ddb0..b7b868c 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -49,9 +49,15 @@ public void Succeed_ShouldCreateSuccessfulResult() { var result = Result.Succeed(); - result.IsSuccess.Should().BeTrue(); - result.IsFailed.Should().BeFalse(); - result.Problem.Should().BeNull(); + result.IsSuccess + .Should() + .BeTrue(); + result.IsFailed + .Should() + .BeFalse(); + result.Problem + .Should() + .BeNull(); } ```