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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ 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 @@
-
-
-
-
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
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 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ 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();
}
```