diff --git a/.vs/Homework/DesignTimeBuild/.dtbcache.v2 b/.vs/Homework/DesignTimeBuild/.dtbcache.v2 new file mode 100644 index 0000000..b62ade6 Binary files /dev/null and b/.vs/Homework/DesignTimeBuild/.dtbcache.v2 differ diff --git a/.vs/Homework/FileContentIndex/89fb9349-a0a3-4232-993a-edfba8f19b6b.vsidx b/.vs/Homework/FileContentIndex/89fb9349-a0a3-4232-993a-edfba8f19b6b.vsidx new file mode 100644 index 0000000..fe43f0c Binary files /dev/null and b/.vs/Homework/FileContentIndex/89fb9349-a0a3-4232-993a-edfba8f19b6b.vsidx differ diff --git a/.vs/Homework/FileContentIndex/da4f42ba-3c78-4845-9eaf-4fd51ddb2a09.vsidx b/.vs/Homework/FileContentIndex/da4f42ba-3c78-4845-9eaf-4fd51ddb2a09.vsidx new file mode 100644 index 0000000..e930847 Binary files /dev/null and b/.vs/Homework/FileContentIndex/da4f42ba-3c78-4845-9eaf-4fd51ddb2a09.vsidx differ diff --git a/.vs/Homework/v17/.futdcache.v2 b/.vs/Homework/v17/.futdcache.v2 new file mode 100644 index 0000000..9f26533 Binary files /dev/null and b/.vs/Homework/v17/.futdcache.v2 differ diff --git a/.vs/Homework/v17/.suo b/.vs/Homework/v17/.suo new file mode 100644 index 0000000..730e38b Binary files /dev/null and b/.vs/Homework/v17/.suo differ diff --git a/.vs/Homework/v17/DocumentLayout.backup.json b/.vs/Homework/v17/DocumentLayout.backup.json new file mode 100644 index 0000000..1c4a49b --- /dev/null +++ b/.vs/Homework/v17/DocumentLayout.backup.json @@ -0,0 +1,37 @@ +{ + "Version": 1, + "WorkspaceRootPath": "C:\\Users\\asus\\Documents\\CSharpHomework2025\\", + "Documents": [ + { + "AbsoluteMoniker": "D:0:0:{FCF2C4E1-EB48-4D53-A8D7-EF81464309F4}|Homework\\Homework.csproj|c:\\users\\asus\\documents\\csharphomework2025\\homework\\program.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", + "RelativeMoniker": "D:0:0:{FCF2C4E1-EB48-4D53-A8D7-EF81464309F4}|Homework\\Homework.csproj|solutionrelative:homework\\program.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" + } + ], + "DocumentGroupContainers": [ + { + "Orientation": 0, + "VerticalTabListWidth": 256, + "DocumentGroups": [ + { + "DockedWidth": 200, + "SelectedChildIndex": 0, + "Children": [ + { + "$type": "Document", + "DocumentIndex": 0, + "Title": "Program.cs", + "DocumentMoniker": "C:\\Users\\asus\\Documents\\CSharpHomework2025\\Homework\\Program.cs", + "RelativeDocumentMoniker": "Homework\\Program.cs", + "ToolTip": "C:\\Users\\asus\\Documents\\CSharpHomework2025\\Homework\\Program.cs", + "RelativeToolTip": "Homework\\Program.cs", + "ViewState": "AgIAAAAAAAAAAAAAAAAAAAwAAAAPAAAAAAAAAA==", + "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", + "WhenOpened": "2025-08-11T06:57:39.763Z", + "EditorCaption": "" + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/.vs/Homework/v17/DocumentLayout.json b/.vs/Homework/v17/DocumentLayout.json new file mode 100644 index 0000000..8473f59 --- /dev/null +++ b/.vs/Homework/v17/DocumentLayout.json @@ -0,0 +1,37 @@ +{ + "Version": 1, + "WorkspaceRootPath": "C:\\Users\\asus\\Documents\\CSharpHomework2025\\", + "Documents": [ + { + "AbsoluteMoniker": "D:0:0:{FCF2C4E1-EB48-4D53-A8D7-EF81464309F4}|Homework\\Homework.csproj|c:\\users\\asus\\documents\\csharphomework2025\\homework\\program.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", + "RelativeMoniker": "D:0:0:{FCF2C4E1-EB48-4D53-A8D7-EF81464309F4}|Homework\\Homework.csproj|solutionrelative:homework\\program.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" + } + ], + "DocumentGroupContainers": [ + { + "Orientation": 0, + "VerticalTabListWidth": 256, + "DocumentGroups": [ + { + "DockedWidth": 200, + "SelectedChildIndex": 0, + "Children": [ + { + "$type": "Document", + "DocumentIndex": 0, + "Title": "Program.cs", + "DocumentMoniker": "C:\\Users\\asus\\Documents\\CSharpHomework2025\\Homework\\Program.cs", + "RelativeDocumentMoniker": "Homework\\Program.cs", + "ToolTip": "C:\\Users\\asus\\Documents\\CSharpHomework2025\\Homework\\Program.cs", + "RelativeToolTip": "Homework\\Program.cs", + "ViewState": "AgIAAAAAAAAAAAAAAAAAABAAAAAFAAAAAAAAAA==", + "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", + "WhenOpened": "2025-08-11T06:57:39.763Z", + "EditorCaption": "" + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/.vs/ProjectEvaluation/homework.metadata.v9.bin b/.vs/ProjectEvaluation/homework.metadata.v9.bin new file mode 100644 index 0000000..92f7585 Binary files /dev/null and b/.vs/ProjectEvaluation/homework.metadata.v9.bin differ diff --git a/.vs/ProjectEvaluation/homework.projects.v9.bin b/.vs/ProjectEvaluation/homework.projects.v9.bin new file mode 100644 index 0000000..274a40a Binary files /dev/null and b/.vs/ProjectEvaluation/homework.projects.v9.bin differ diff --git a/.vs/ProjectEvaluation/homework.strings.v9.bin b/.vs/ProjectEvaluation/homework.strings.v9.bin new file mode 100644 index 0000000..aa77f63 Binary files /dev/null and b/.vs/ProjectEvaluation/homework.strings.v9.bin differ diff --git a/Homework/Homework.csproj b/Homework/Homework.csproj index fd4bd08..b186ee9 100644 --- a/Homework/Homework.csproj +++ b/Homework/Homework.csproj @@ -2,9 +2,10 @@ Exe - net9.0 + net8.0 enable enable + 0263fb07-2244-4f32-bcd4-45a1046a2b57 diff --git a/Homework/Homework.csproj.user b/Homework/Homework.csproj.user new file mode 100644 index 0000000..e7b749a --- /dev/null +++ b/Homework/Homework.csproj.user @@ -0,0 +1,9 @@ + + + + ProjectDebugger + + + WSL + + \ No newline at end of file diff --git a/Homework/Program.cs b/Homework/Program.cs index b728a41..a2e0fa5 100644 --- a/Homework/Program.cs +++ b/Homework/Program.cs @@ -1,271 +1,469 @@ -// See https://aka.ms/new-console-template for more information -using System; -using System.Collections.Generic; -using System.IO; - -namespace StudentManagementSystem -{ - // 成绩等级枚举 - public enum Grade - { - // TODO: 定义成绩等级 F(0), D(60), C(70), B(80), A(90) - - } - - // 泛型仓储接口 - public interface IRepository - { - // TODO: 定义接口方法 - // Add(T item) - // Remove(T item) 返回bool - // GetAll() 返回List - // Find(Func predicate) 返回List - - } - - // 学生类 - public class Student : IComparable - { - // TODO: 定义字段 StudentId, Name, Age - - - public Student(string studentId, string name, int age) - { - // TODO: 实现构造方法,包含参数验证(空值检查) - - } - - public override string ToString() - { - // TODO: 返回格式化的学生信息字符串 - - } - - // TODO: 实现IComparable接口,按学号排序 - // 提示:使用string.Compare方法 - public int CompareTo(Student? other) - { - - } - - public override bool Equals(object? obj) - { - return obj is Student student && StudentId == student.StudentId; - } - - public override int GetHashCode() - { - return StudentId?.GetHashCode() ?? 0; - } - } - - // 成绩类 - public class Score - { - // TODO: 定义字段 Subject, Points - - - public Score(string subject, double points) - { - // TODO: 实现构造方法,包含参数验证 - - } - - public override string ToString() - { - // TODO: 返回格式化的成绩信息 - - } - } - - // 学生管理类 - public class StudentManager : IRepository - { - // TODO: 定义私有字段存储学生列表 - // 提示:使用List存储 - - - public void Add(Student item) - { - // TODO: 实现添加学生的逻辑 - // 1. 参数验证 - // 2. 添加到列表 - - } - - public bool Remove(Student item) - { - // TODO: 实现Remove方法 - - } - - public List GetAll() - { - // TODO: 返回学生列表的副本 - - } - - public List Find(Func predicate) - { - // TODO: 使用foreach循环查找符合条件的学生 - - } - - // 查找年龄在指定范围内的学生 - public List GetStudentsByAge(int minAge, int maxAge) - { - // TODO: 使用foreach循环和if判断实现年龄范围查询 - - } - } - - // 成绩管理类 - public class ScoreManager - { - // TODO: 定义私有字段存储成绩字典 - // 提示:使用Dictionary>存储 - - - public void AddScore(string studentId, Score score) - { - // TODO: 实现添加成绩的逻辑 - // 1. 参数验证 - // 2. 初始化学生成绩列表(如不存在) - // 3. 添加成绩 - - } - - public List GetStudentScores(string studentId) - { - // TODO: 获取指定学生的所有成绩 - - } - - public double CalculateAverage(string studentId) - { - // TODO: 计算指定学生的平均分 - // 提示:使用foreach循环计算总分,然后除以科目数 - - } - - // TODO: 使用模式匹配实现成绩等级转换 - public Grade GetGrade(double score) - { - - } - - public List<(string StudentId, double Average)> GetTopStudents(int count) - { - // TODO: 使用简单循环获取平均分最高的学生 - // 提示:可以先计算所有学生的平均分,然后排序取前count个 - - } - - public Dictionary> GetAllScores() - { - return new Dictionary>(scores); - } - } - - // 数据管理类 - public class DataManager - { - public void SaveStudentsToFile(List students, string filePath) - { - // TODO: 实现保存学生数据到文件 - // 提示:使用StreamWriter,格式为CSV - try - { - // 在这里实现文件写入逻辑 - - } - catch (Exception ex) - { - Console.WriteLine($"保存文件时发生错误: {ex.Message}"); - } - } - - public List LoadStudentsFromFile(string filePath) - { - List students = new List(); - - // TODO: 实现从文件读取学生数据 - // 提示:使用StreamReader,解析CSV格式 - try - { - // 在这里实现文件读取逻辑 - - } - catch (Exception ex) - { - Console.WriteLine($"读取文件时发生错误: {ex.Message}"); - } - - return students; - } - } - - // 主程序 - class Program - { - static void Main(string[] args) - { - Console.WriteLine("=== 学生成绩管理系统 ===\n"); - - // 创建管理器实例 - var studentManager = new StudentManager(); - var scoreManager = new ScoreManager(); - var dataManager = new DataManager(); - - try - { - // 1. 学生数据(共3个学生) - Console.WriteLine("1. 添加学生信息:"); - studentManager.Add(new Student("2021001", "张三", 20)); - studentManager.Add(new Student("2021002", "李四", 19)); - studentManager.Add(new Student("2021003", "王五", 21)); - Console.WriteLine("学生信息添加完成"); - - // 2. 成绩数据(每个学生各2门课程) - Console.WriteLine("\n2. 添加成绩信息:"); - scoreManager.AddScore("2021001", new Score("数学", 95.5)); - scoreManager.AddScore("2021001", new Score("英语", 87.0)); - - scoreManager.AddScore("2021002", new Score("数学", 78.5)); - scoreManager.AddScore("2021002", new Score("英语", 85.5)); - - scoreManager.AddScore("2021003", new Score("数学", 88.0)); - scoreManager.AddScore("2021003", new Score("英语", 92.0)); - Console.WriteLine("成绩信息添加完成"); - - // 3. 测试年龄范围查询 - Console.WriteLine("\n3. 查找年龄在19-20岁的学生:"); - // TODO: 调用GetStudentsByAge方法并显示结果 - - - // 4. 显示学生成绩统计 - Console.WriteLine("\n4. 学生成绩统计:"); - // TODO: 遍历所有学生,显示其成绩、平均分和等级 - - - // 5. 显示排名(简化版) - Console.WriteLine("\n5. 平均分最高的学生:"); - // TODO: 调用GetTopStudents(1)方法显示第一名 - - - // 6. 文件操作 - Console.WriteLine("\n6. 数据持久化演示:"); - // TODO: 保存和读取学生文件 - - - } - catch (Exception ex) - { - Console.WriteLine($"程序执行过程中发生错误: {ex.Message}"); - } - - Console.WriteLine("\n程序执行完毕,按任意键退出..."); - Console.ReadKey(); - } - } +// See https://aka.ms/new-console-template for more information +using System; +using System.Collections.Generic; +using System.IO; + +namespace StudentManagementSystem +{ + // 成绩等级枚举 + public enum Grade + { + // TODO: 定义成绩等级 F(0), D(60), C(70), B(80), A(90) + F = 0, + D = 60, + C = 70, + B = 80, + A = 90 + } + + // 泛型仓储接口(主要给Student类使用) + public interface IRepository + { + // TODO: 定义接口方法 + // Add(T item) + // Remove(T item) 返回bool + // GetAll() 返回List + // Find(Func predicate) 返回List + void Add(T item); + + bool Remove(T item); + + List GetAll(); + + List Find(Func predicate); + } + + // 学生类 + public class Student : IComparable + { + // TODO: 定义字段 StudentId, Name, Age + public string StudentId; + public string Name; + public int Age; + + public Student(string studentId, string name, int age) + { + // TODO: 实现构造方法,包含参数验证(空值检查) + try + { + if (string.IsNullOrWhiteSpace(studentId)) + throw new ArgumentException("StudentId 不能为空"); + if (string.IsNullOrWhiteSpace(name)) + throw new ArgumentException("Name 不能为空"); + if (age < 0) + throw new ArgumentException("Age 不能为负数"); + + this.StudentId = studentId; + this.Name = name; + this.Age = age; + } + catch (ArgumentException ex) + { + Console.WriteLine($"参数错误: {ex.Message}"); + throw; + } + } + + public override string ToString() + { + // TODO: 返回格式化的学生信息字符串 + return $"ID: {StudentId}, Name: {Name}, Age: {Age}"; + } + + // TODO: 实现IComparable接口,按学号排序 + // 提示:使用string.Compare方法 + public int CompareTo(Student? other) + { + if (other == null) return 1; + return string.Compare(this.StudentId, other.StudentId, StringComparison.Ordinal); + } + + public override bool Equals(object? obj) + { + return obj is Student student && StudentId == student.StudentId; + } + + public override int GetHashCode() + { + return StudentId?.GetHashCode() ?? 0; + } + } + + // 成绩类 + public class Score + { + // TODO: 定义字段 Subject, Points + public string Subject; + public double Points; + + public Score(string subject, double points) + { + // TODO: 实现构造方法,包含参数验证 + try + { + if (string.IsNullOrWhiteSpace(subject)) + throw new ArgumentException("Subject 不能为空"); + if (points < 0) + throw new ArgumentException("Points 不能为负数"); + + this.Subject = subject; + this.Points = points; + } + catch (ArgumentException ex) + { + Console.WriteLine($"参数错误: {ex.Message}"); + throw; + } + } + + public override string ToString() + { + // TODO: 返回格式化的成绩信息 + return $"Subject: {Subject}, Points: {Points}"; + } + } + + // 学生管理类 + public class StudentManager : IRepository + { + // TODO: 定义私有字段存储学生列表 + // 提示:使用List存储 + private List studentsList = new List(); + + public void Add(Student item) + { + // TODO: 实现添加学生的逻辑 + // 1. 参数验证 + // 2. 添加到列表 + try + { + if (item == null) + throw new ArgumentException("Student 对象不能为空"); + if (string.IsNullOrWhiteSpace(item.StudentId)) + throw new ArgumentException("StudentId 不能为空"); + if (string.IsNullOrWhiteSpace(item.Name)) + throw new ArgumentException("Name 不能为空"); + if (item.Age < 0) + throw new ArgumentException("Age 不能为负数"); + + studentsList.Add(item); + } + catch (ArgumentException ex) + { + Console.WriteLine($"参数错误: {ex.Message}"); + throw; + } + } + + public bool Remove(Student item) + { + // TODO: 实现Remove方法 + return studentsList.Remove(item); + } + + public List GetAll() + { + // TODO: 返回学生列表的副本 + return studentsList; + } + + public List Find(Func predicate) // 查找所有符合条件(待定义)的学生 + { + // TODO: 使用foreach循环查找符合条件的学生 + List result = new List(); + + foreach (var student in studentsList) { + // 调用 predicate 委托(调用时才会执行具体条件,比如 Age >= 18) + if (predicate(student)) + { + result.Add(student); + } + } + + return result; + } + + // 查找年龄在指定范围内的学生 + public List GetStudentsByAge(int minAge, int maxAge) + { + // TODO: 使用foreach循环和if判断实现年龄范围查询 + List result = new List(); + + foreach (Student student in studentsList) { + if (student.Age >= minAge && student.Age <= maxAge) { + result.Add(student); + } + } + + return result; + } + } + + // 成绩管理类 + public class ScoreManager + { + // TODO: 定义私有字段存储成绩字典 + // 提示:使用Dictionary>存储 + private Dictionary> scoresDict = new Dictionary>(); // 一个同学有多个成绩 + + public void AddScore(string studentId, Score score) + { + // TODO: 实现添加成绩的逻辑 + // 1. 参数验证 + // 2. 初始化学生成绩列表(如不存在) + // 3. 添加成绩 + try + { + if (string.IsNullOrWhiteSpace(studentId)) + throw new ArgumentException("StudentId 不能为空"); + if (score == null) + throw new ArgumentException("Score 对象不能为空"); + if (string.IsNullOrWhiteSpace(score.Subject)) + throw new ArgumentException("Subject 不能为空"); + if (score.Points < 0) + throw new ArgumentException("Points 不能为负数"); + + if (!scoresDict.ContainsKey(studentId)) + { + scoresDict[studentId] = new List(); + } + + scoresDict[studentId].Add(score); + } + catch (ArgumentException ex) + { + Console.WriteLine($"参数错误: {ex.Message}"); + throw; + } + } + + public List GetStudentScores(string studentId) + { + // TODO: 获取指定学生的所有成绩 + if (scoresDict.ContainsKey(studentId)) + { + return new List(scoresDict[studentId]); // 返回副本,防止外部修改 + } + return new List(); // 如果没找到,返回空列表 + } + + public double CalculateAverage(string studentId) + { + // TODO: 计算指定学生的平均分 + // 提示:使用foreach循环计算总分,然后除以科目数 + if (!scoresDict.ContainsKey(studentId) || scoresDict[studentId].Count == 0) return 0; + + double sum = 0; + foreach (var score in scoresDict[studentId]) + { + sum += score.Points; + } + + return sum / scoresDict[studentId].Count; + } + + // TODO: 使用模式匹配实现成绩等级转换 + public Grade GetGrade(double score) + { + switch ((int)score) // 转成 int 避免浮点判断误差 + { + case >= 90: + return Grade.A; + case >= 80: + return Grade.B; + case >= 70: + return Grade.C; + case >= 60: + return Grade.D; + default: + return Grade.F; + } + } + + public List<(string StudentId, double Average)> GetTopStudents(int count) + { + // TODO: 使用简单循环获取平均分最高的学生 + // 提示:可以先计算所有学生的平均分,然后排序取前count个 + var averages = new List<(string StudentId, double Average)>(); + + foreach (var kvp in scoresDict) + { + double avg = CalculateAverage(kvp.Key); + averages.Add((kvp.Key, avg)); + } + + averages.Sort((a, b) => b.Average.CompareTo(a.Average)); // 降序 + return averages.Take(count).ToList(); + } + + public Dictionary> GetAllScores() + { + return new Dictionary>(scoresDict); + } + } + + // 数据管理类 + public class DataManager + { + public void SaveStudentsToFile(List students, string filePath) + { + // TODO: 实现保存学生数据到文件 + // 提示:使用StreamWriter,格式为CSV + try + { + // 在这里实现文件写入逻辑 + using (StreamWriter writer = new StreamWriter(filePath)) + { + // 写表头 + writer.WriteLine("StudentId,Name,Age"); + + // 写每个学生数据 + foreach (var student in students) + { + writer.WriteLine($"{student.StudentId},{student.Name},{student.Age}"); + } + } + Console.WriteLine("学生数据已保存到文件。"); + } + catch (Exception ex) + { + Console.WriteLine($"保存文件时发生错误: {ex.Message}"); + } + } + + public List LoadStudentsFromFile(string filePath) + { + List students = new List(); + + // TODO: 实现从文件读取学生数据 + // 提示:使用StreamReader,解析CSV格式 + try + { + // 在这里实现文件读取逻辑 + using (StreamReader reader = new StreamReader(filePath)) + { + string? line; + bool isFirstLine = true; + + while ((line = reader.ReadLine()) != null) + { + // 跳过表头 + if (isFirstLine) + { + isFirstLine = false; + continue; + } + + var parts = line.Split(','); + if (parts.Length == 3) + { + string studentId = parts[0]; + string name = parts[1]; + if (int.TryParse(parts[2], out int age)) + { + students.Add(new Student(studentId, name, age)); + } + } + } + } + } + catch (Exception ex) + { + Console.WriteLine($"读取文件时发生错误: {ex.Message}"); + } + + return students; + } + } + + // 主程序 + class Program + { + static void Main(string[] args) + { + Console.WriteLine("=== 学生成绩管理系统 ===\n"); + + // 创建管理器实例 + var studentManager = new StudentManager(); + var scoreManager = new ScoreManager(); + var dataManager = new DataManager(); + + try + { + // 1. 学生数据(共3个学生) + Console.WriteLine("1. 添加学生信息:"); + studentManager.Add(new Student("2021001", "张三", 20)); + studentManager.Add(new Student("2021002", "李四", 19)); + studentManager.Add(new Student("2021003", "王五", 21)); + Console.WriteLine("学生信息添加完成"); + + // 2. 成绩数据(每个学生各2门课程) + Console.WriteLine("\n2. 添加成绩信息:"); + scoreManager.AddScore("2021001", new Score("数学", 95.5)); + scoreManager.AddScore("2021001", new Score("英语", 87.0)); + + scoreManager.AddScore("2021002", new Score("数学", 78.5)); + scoreManager.AddScore("2021002", new Score("英语", 85.5)); + + scoreManager.AddScore("2021003", new Score("数学", 88.0)); + scoreManager.AddScore("2021003", new Score("英语", 92.0)); + Console.WriteLine("成绩信息添加完成"); + + // 3. 测试年龄范围查询 + Console.WriteLine("\n3. 查找年龄在19-20岁的学生:"); + // TODO: 调用GetStudentsByAge方法并显示结果 + var ageFiltered = studentManager.GetStudentsByAge(19, 20); + foreach (var student in ageFiltered) + { + Console.WriteLine(student); + } + + // 4. 显示学生成绩统计 + Console.WriteLine("\n4. 学生成绩统计:"); + // TODO: 遍历所有学生,显示其成绩、平均分和等级 + foreach (var student in studentManager.GetAll()) { + var scoreList = scoreManager.GetStudentScores(student.StudentId); + var average = scoreManager.CalculateAverage(student.StudentId); + var grade = scoreManager.GetGrade(average); + + Console.WriteLine($"{student}"); // 隐式调用 student 对象的 ToString() 方法 + Console.WriteLine($"成绩: {string.Join(", ", scoreList.Select(s => s.ToString()))}"); + Console.WriteLine($"平均分: {average:F1}, 等级: {grade}"); + Console.WriteLine(); + } + + // 5. 显示排名(简化版) + Console.WriteLine("\n5. 平均分最高的学生:"); + // TODO: 调用GetTopStudents(1)方法显示第一名 + var topStudent = scoreManager.GetTopStudents(1).FirstOrDefault(); + if (topStudent != default) + { + var student = studentManager.Find(s => s.StudentId == topStudent.StudentId).First(); + Console.WriteLine($"{student} - 平均分: {topStudent.Average:F1}"); + } + + // 6. 文件操作 + Console.WriteLine("\n6. 数据持久化演示:"); + // TODO: 保存和读取学生文件 + string filePath = "students.csv"; + dataManager.SaveStudentsToFile(studentManager.GetAll(), filePath); + Console.WriteLine($"学生数据已保存到 {filePath}"); + + var loadedStudents = dataManager.LoadStudentsFromFile(filePath); + Console.WriteLine($"从文件加载的学生数据:"); + foreach (var student in loadedStudents) + { + Console.WriteLine(student); + } + + } + catch (Exception ex) + { + Console.WriteLine($"程序执行过程中发生错误: {ex.Message}"); + } + + Console.WriteLine("\n程序执行完毕,按任意键退出..."); + Console.ReadKey(); + } + } } \ No newline at end of file diff --git a/Homework/Properties/launchSettings.json b/Homework/Properties/launchSettings.json new file mode 100644 index 0000000..12a1f50 --- /dev/null +++ b/Homework/Properties/launchSettings.json @@ -0,0 +1,18 @@ +{ + "profiles": { + "Homework": { + "commandName": "Project" + }, + "WSL": { + "commandName": "WSL", + "launchBrowser": true, + "launchUrl": "https://localhost:5001", + "environmentVariables": { + "ASPNETCORE_URLS": "https://localhost:5001;http://localhost:5000", + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "executablePath": "/home/yqh0725/.dotnet/dotnet", + "distributionName": "Ubuntu-24.04" + } + } +} \ No newline at end of file diff --git a/Homework/bin/Debug/net8.0/Homework.deps.json b/Homework/bin/Debug/net8.0/Homework.deps.json new file mode 100644 index 0000000..8646ce1 --- /dev/null +++ b/Homework/bin/Debug/net8.0/Homework.deps.json @@ -0,0 +1,23 @@ +{ + "runtimeTarget": { + "name": ".NETCoreApp,Version=v8.0", + "signature": "" + }, + "compilationOptions": {}, + "targets": { + ".NETCoreApp,Version=v8.0": { + "Homework/1.0.0": { + "runtime": { + "Homework.dll": {} + } + } + } + }, + "libraries": { + "Homework/1.0.0": { + "type": "project", + "serviceable": false, + "sha512": "" + } + } +} \ No newline at end of file diff --git a/Homework/bin/Debug/net8.0/Homework.dll b/Homework/bin/Debug/net8.0/Homework.dll new file mode 100644 index 0000000..07e3f9c Binary files /dev/null and b/Homework/bin/Debug/net8.0/Homework.dll differ diff --git a/Homework/bin/Debug/net8.0/Homework.exe b/Homework/bin/Debug/net8.0/Homework.exe new file mode 100644 index 0000000..31bf978 Binary files /dev/null and b/Homework/bin/Debug/net8.0/Homework.exe differ diff --git a/Homework/bin/Debug/net8.0/Homework.pdb b/Homework/bin/Debug/net8.0/Homework.pdb new file mode 100644 index 0000000..abe0132 Binary files /dev/null and b/Homework/bin/Debug/net8.0/Homework.pdb differ diff --git a/Homework/bin/Debug/net8.0/Homework.runtimeconfig.json b/Homework/bin/Debug/net8.0/Homework.runtimeconfig.json new file mode 100644 index 0000000..1de3a5d --- /dev/null +++ b/Homework/bin/Debug/net8.0/Homework.runtimeconfig.json @@ -0,0 +1,12 @@ +{ + "runtimeOptions": { + "tfm": "net8.0", + "framework": { + "name": "Microsoft.NETCore.App", + "version": "8.0.0" + }, + "configProperties": { + "System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization": false + } + } +} \ No newline at end of file diff --git a/Homework/obj/Debug/net8.0/.NETCoreApp,Version=v8.0.AssemblyAttributes.cs b/Homework/obj/Debug/net8.0/.NETCoreApp,Version=v8.0.AssemblyAttributes.cs new file mode 100644 index 0000000..678fc5f --- /dev/null +++ b/Homework/obj/Debug/net8.0/.NETCoreApp,Version=v8.0.AssemblyAttributes.cs @@ -0,0 +1,4 @@ +// +using System; +using System.Reflection; +[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v8.0", FrameworkDisplayName = ".NET 8.0")] diff --git a/Homework/obj/Debug/net8.0/Homework.AssemblyInfo.cs b/Homework/obj/Debug/net8.0/Homework.AssemblyInfo.cs new file mode 100644 index 0000000..9a9e8c1 --- /dev/null +++ b/Homework/obj/Debug/net8.0/Homework.AssemblyInfo.cs @@ -0,0 +1,23 @@ +//------------------------------------------------------------------------------ +// +// 此代码由工具生成。 +// 运行时版本:4.0.30319.42000 +// +// 对此文件的更改可能会导致不正确的行为,并且如果 +// 重新生成代码,这些更改将会丢失。 +// +//------------------------------------------------------------------------------ + +using System; +using System.Reflection; + +[assembly: System.Reflection.AssemblyCompanyAttribute("Homework")] +[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] +[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] +[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+3439ef5fa11ea974a437d4e71f6d7e82763f847e")] +[assembly: System.Reflection.AssemblyProductAttribute("Homework")] +[assembly: System.Reflection.AssemblyTitleAttribute("Homework")] +[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] + +// 由 MSBuild WriteCodeFragment 类生成。 + diff --git a/Homework/obj/Debug/net8.0/Homework.AssemblyInfoInputs.cache b/Homework/obj/Debug/net8.0/Homework.AssemblyInfoInputs.cache new file mode 100644 index 0000000..bb9ca74 --- /dev/null +++ b/Homework/obj/Debug/net8.0/Homework.AssemblyInfoInputs.cache @@ -0,0 +1 @@ +3e80dbcd1ff00cdcba3863df6d82f161321270261e04e5ccbff3d91e740385e0 diff --git a/Homework/obj/Debug/net8.0/Homework.GeneratedMSBuildEditorConfig.editorconfig b/Homework/obj/Debug/net8.0/Homework.GeneratedMSBuildEditorConfig.editorconfig new file mode 100644 index 0000000..44ebeda --- /dev/null +++ b/Homework/obj/Debug/net8.0/Homework.GeneratedMSBuildEditorConfig.editorconfig @@ -0,0 +1,13 @@ +is_global = true +build_property.TargetFramework = net8.0 +build_property.TargetPlatformMinVersion = +build_property.UsingMicrosoftNETSdkWeb = +build_property.ProjectTypeGuids = +build_property.InvariantGlobalization = +build_property.PlatformNeutralAssembly = +build_property.EnforceExtendedAnalyzerRules = +build_property._SupportedPlatformList = Linux,macOS,Windows +build_property.RootNamespace = Homework +build_property.ProjectDir = C:\Users\asus\Documents\CSharpHomework2025\Homework\ +build_property.EnableComHosting = +build_property.EnableGeneratedComInterfaceComImportInterop = diff --git a/Homework/obj/Debug/net8.0/Homework.GlobalUsings.g.cs b/Homework/obj/Debug/net8.0/Homework.GlobalUsings.g.cs new file mode 100644 index 0000000..ac22929 --- /dev/null +++ b/Homework/obj/Debug/net8.0/Homework.GlobalUsings.g.cs @@ -0,0 +1,8 @@ +// +global using global::System; +global using global::System.Collections.Generic; +global using global::System.IO; +global using global::System.Linq; +global using global::System.Net.Http; +global using global::System.Threading; +global using global::System.Threading.Tasks; diff --git a/Homework/obj/Debug/net8.0/Homework.assets.cache b/Homework/obj/Debug/net8.0/Homework.assets.cache new file mode 100644 index 0000000..c24e61a Binary files /dev/null and b/Homework/obj/Debug/net8.0/Homework.assets.cache differ diff --git a/Homework/obj/Debug/net8.0/Homework.csproj.BuildWithSkipAnalyzers b/Homework/obj/Debug/net8.0/Homework.csproj.BuildWithSkipAnalyzers new file mode 100644 index 0000000..e69de29 diff --git a/Homework/obj/Debug/net8.0/Homework.csproj.CoreCompileInputs.cache b/Homework/obj/Debug/net8.0/Homework.csproj.CoreCompileInputs.cache new file mode 100644 index 0000000..cc1c7a9 --- /dev/null +++ b/Homework/obj/Debug/net8.0/Homework.csproj.CoreCompileInputs.cache @@ -0,0 +1 @@ +619fd6426a171ea713dca051b7451581df6d0cf9a3eefd9a3dc3d449cfe0197a diff --git a/Homework/obj/Debug/net8.0/Homework.csproj.FileListAbsolute.txt b/Homework/obj/Debug/net8.0/Homework.csproj.FileListAbsolute.txt new file mode 100644 index 0000000..00356ae --- /dev/null +++ b/Homework/obj/Debug/net8.0/Homework.csproj.FileListAbsolute.txt @@ -0,0 +1,15 @@ +C:\Users\asus\Documents\CSharpHomework2025\Homework\obj\Debug\net8.0\Homework.GeneratedMSBuildEditorConfig.editorconfig +C:\Users\asus\Documents\CSharpHomework2025\Homework\obj\Debug\net8.0\Homework.AssemblyInfoInputs.cache +C:\Users\asus\Documents\CSharpHomework2025\Homework\obj\Debug\net8.0\Homework.AssemblyInfo.cs +C:\Users\asus\Documents\CSharpHomework2025\Homework\obj\Debug\net8.0\Homework.csproj.CoreCompileInputs.cache +C:\Users\asus\Documents\CSharpHomework2025\Homework\obj\Debug\net8.0\Homework.sourcelink.json +C:\Users\asus\Documents\CSharpHomework2025\Homework\bin\Debug\net8.0\Homework.exe +C:\Users\asus\Documents\CSharpHomework2025\Homework\bin\Debug\net8.0\Homework.deps.json +C:\Users\asus\Documents\CSharpHomework2025\Homework\bin\Debug\net8.0\Homework.runtimeconfig.json +C:\Users\asus\Documents\CSharpHomework2025\Homework\bin\Debug\net8.0\Homework.dll +C:\Users\asus\Documents\CSharpHomework2025\Homework\bin\Debug\net8.0\Homework.pdb +C:\Users\asus\Documents\CSharpHomework2025\Homework\obj\Debug\net8.0\Homework.dll +C:\Users\asus\Documents\CSharpHomework2025\Homework\obj\Debug\net8.0\refint\Homework.dll +C:\Users\asus\Documents\CSharpHomework2025\Homework\obj\Debug\net8.0\Homework.pdb +C:\Users\asus\Documents\CSharpHomework2025\Homework\obj\Debug\net8.0\Homework.genruntimeconfig.cache +C:\Users\asus\Documents\CSharpHomework2025\Homework\obj\Debug\net8.0\ref\Homework.dll diff --git a/Homework/obj/Debug/net8.0/Homework.dll b/Homework/obj/Debug/net8.0/Homework.dll new file mode 100644 index 0000000..07e3f9c Binary files /dev/null and b/Homework/obj/Debug/net8.0/Homework.dll differ diff --git a/Homework/obj/Debug/net8.0/Homework.genruntimeconfig.cache b/Homework/obj/Debug/net8.0/Homework.genruntimeconfig.cache new file mode 100644 index 0000000..c55b11c --- /dev/null +++ b/Homework/obj/Debug/net8.0/Homework.genruntimeconfig.cache @@ -0,0 +1 @@ +11eb445402450817906728b74904875c570939697300a8d70211b703013b879a diff --git a/Homework/obj/Debug/net8.0/Homework.pdb b/Homework/obj/Debug/net8.0/Homework.pdb new file mode 100644 index 0000000..abe0132 Binary files /dev/null and b/Homework/obj/Debug/net8.0/Homework.pdb differ diff --git a/Homework/obj/Debug/net8.0/Homework.sourcelink.json b/Homework/obj/Debug/net8.0/Homework.sourcelink.json new file mode 100644 index 0000000..a789f11 --- /dev/null +++ b/Homework/obj/Debug/net8.0/Homework.sourcelink.json @@ -0,0 +1 @@ +{"documents":{"C:\\Users\\asus\\Documents\\CSharpHomework2025\\*":"https://raw.githubusercontent.com/QiHanYong/CSharpHomework2025/3439ef5fa11ea974a437d4e71f6d7e82763f847e/*"}} \ No newline at end of file diff --git a/Homework/obj/Debug/net8.0/apphost.exe b/Homework/obj/Debug/net8.0/apphost.exe new file mode 100644 index 0000000..31bf978 Binary files /dev/null and b/Homework/obj/Debug/net8.0/apphost.exe differ diff --git a/Homework/obj/Debug/net8.0/ref/Homework.dll b/Homework/obj/Debug/net8.0/ref/Homework.dll new file mode 100644 index 0000000..9886afc Binary files /dev/null and b/Homework/obj/Debug/net8.0/ref/Homework.dll differ diff --git a/Homework/obj/Debug/net8.0/refint/Homework.dll b/Homework/obj/Debug/net8.0/refint/Homework.dll new file mode 100644 index 0000000..9886afc Binary files /dev/null and b/Homework/obj/Debug/net8.0/refint/Homework.dll differ diff --git a/Homework/obj/Debug/net9.0/Homework.GlobalUsings.g.cs b/Homework/obj/Debug/net9.0/Homework.GlobalUsings.g.cs index 8578f3d..ac22929 100644 --- a/Homework/obj/Debug/net9.0/Homework.GlobalUsings.g.cs +++ b/Homework/obj/Debug/net9.0/Homework.GlobalUsings.g.cs @@ -1,8 +1,8 @@ -// -global using global::System; -global using global::System.Collections.Generic; -global using global::System.IO; -global using global::System.Linq; -global using global::System.Net.Http; -global using global::System.Threading; -global using global::System.Threading.Tasks; +// +global using global::System; +global using global::System.Collections.Generic; +global using global::System.IO; +global using global::System.Linq; +global using global::System.Net.Http; +global using global::System.Threading; +global using global::System.Threading.Tasks; diff --git a/Homework/obj/Debug/net9.0/Homework.assets.cache b/Homework/obj/Debug/net9.0/Homework.assets.cache index e69f95a..5a5c167 100644 Binary files a/Homework/obj/Debug/net9.0/Homework.assets.cache and b/Homework/obj/Debug/net9.0/Homework.assets.cache differ diff --git a/Homework/obj/Homework.csproj.nuget.dgspec.json b/Homework/obj/Homework.csproj.nuget.dgspec.json index 2ef35da..a9a50ea 100644 --- a/Homework/obj/Homework.csproj.nuget.dgspec.json +++ b/Homework/obj/Homework.csproj.nuget.dgspec.json @@ -1,73 +1,72 @@ -{ - "format": 1, - "restore": { - "C:\\Users\\ms169\\Desktop\\Homework\\Homework\\Homework.csproj": {} - }, - "projects": { - "C:\\Users\\ms169\\Desktop\\Homework\\Homework\\Homework.csproj": { - "version": "1.0.0", - "restore": { - "projectUniqueName": "C:\\Users\\ms169\\Desktop\\Homework\\Homework\\Homework.csproj", - "projectName": "Homework", - "projectPath": "C:\\Users\\ms169\\Desktop\\Homework\\Homework\\Homework.csproj", - "packagesPath": "C:\\Users\\ms169\\.nuget\\packages\\", - "outputPath": "C:\\Users\\ms169\\Desktop\\Homework\\Homework\\obj\\", - "projectStyle": "PackageReference", - "fallbackFolders": [ - "C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages" - ], - "configFilePaths": [ - "C:\\Users\\ms169\\AppData\\Roaming\\NuGet\\NuGet.Config", - "C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.FallbackLocation.config", - "C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.Offline.config" - ], - "originalTargetFrameworks": [ - "net9.0" - ], - "sources": { - "C:\\Program Files (x86)\\Microsoft SDKs\\NuGetPackages\\": {}, - "https://api.nuget.org/v3/index.json": {} - }, - "frameworks": { - "net9.0": { - "targetAlias": "net9.0", - "projectReferences": {} - } - }, - "warningProperties": { - "warnAsError": [ - "NU1605" - ] - }, - "restoreAuditProperties": { - "enableAudit": "true", - "auditLevel": "low", - "auditMode": "direct" - }, - "SdkAnalysisLevel": "9.0.200" - }, - "frameworks": { - "net9.0": { - "targetAlias": "net9.0", - "imports": [ - "net461", - "net462", - "net47", - "net471", - "net472", - "net48", - "net481" - ], - "assetTargetFallback": true, - "warn": true, - "frameworkReferences": { - "Microsoft.NETCore.App": { - "privateAssets": "all" - } - }, - "runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\9.0.201/PortableRuntimeIdentifierGraph.json" - } - } - } - } +{ + "format": 1, + "restore": { + "C:\\Users\\asus\\Documents\\CSharpHomework2025\\Homework\\Homework.csproj": {} + }, + "projects": { + "C:\\Users\\asus\\Documents\\CSharpHomework2025\\Homework\\Homework.csproj": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "C:\\Users\\asus\\Documents\\CSharpHomework2025\\Homework\\Homework.csproj", + "projectName": "Homework", + "projectPath": "C:\\Users\\asus\\Documents\\CSharpHomework2025\\Homework\\Homework.csproj", + "packagesPath": "C:\\Users\\asus\\.nuget\\packages\\", + "outputPath": "C:\\Users\\asus\\Documents\\CSharpHomework2025\\Homework\\obj\\", + "projectStyle": "PackageReference", + "fallbackFolders": [ + "C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages" + ], + "configFilePaths": [ + "C:\\Users\\asus\\AppData\\Roaming\\NuGet\\NuGet.Config", + "C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.FallbackLocation.config", + "C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.Offline.config" + ], + "originalTargetFrameworks": [ + "net8.0" + ], + "sources": { + "C:\\Program Files (x86)\\Microsoft SDKs\\NuGetPackages\\": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net8.0": { + "targetAlias": "net8.0", + "projectReferences": {} + } + }, + "warningProperties": { + "warnAsError": [ + "NU1605" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "direct" + } + }, + "frameworks": { + "net8.0": { + "targetAlias": "net8.0", + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\8.0.400/PortableRuntimeIdentifierGraph.json" + } + } + } + } } \ No newline at end of file diff --git a/Homework/obj/Homework.csproj.nuget.g.props b/Homework/obj/Homework.csproj.nuget.g.props index 9026052..5235266 100644 --- a/Homework/obj/Homework.csproj.nuget.g.props +++ b/Homework/obj/Homework.csproj.nuget.g.props @@ -1,16 +1,16 @@ - - - - True - NuGet - $(MSBuildThisFileDirectory)project.assets.json - $(UserProfile)\.nuget\packages\ - C:\Users\ms169\.nuget\packages\;C:\Program Files (x86)\Microsoft Visual Studio\Shared\NuGetPackages - PackageReference - 6.13.1 - - - - - + + + + True + NuGet + $(MSBuildThisFileDirectory)project.assets.json + $(UserProfile)\.nuget\packages\ + C:\Users\asus\.nuget\packages\;C:\Program Files (x86)\Microsoft Visual Studio\Shared\NuGetPackages + PackageReference + 6.11.0 + + + + + \ No newline at end of file diff --git a/Homework/obj/project.assets.json b/Homework/obj/project.assets.json index 907663e..033cafa 100644 --- a/Homework/obj/project.assets.json +++ b/Homework/obj/project.assets.json @@ -1,79 +1,78 @@ -{ - "version": 3, - "targets": { - "net9.0": {} - }, - "libraries": {}, - "projectFileDependencyGroups": { - "net9.0": [] - }, - "packageFolders": { - "C:\\Users\\ms169\\.nuget\\packages\\": {}, - "C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages": {} - }, - "project": { - "version": "1.0.0", - "restore": { - "projectUniqueName": "C:\\Users\\ms169\\Desktop\\Homework\\Homework\\Homework.csproj", - "projectName": "Homework", - "projectPath": "C:\\Users\\ms169\\Desktop\\Homework\\Homework\\Homework.csproj", - "packagesPath": "C:\\Users\\ms169\\.nuget\\packages\\", - "outputPath": "C:\\Users\\ms169\\Desktop\\Homework\\Homework\\obj\\", - "projectStyle": "PackageReference", - "fallbackFolders": [ - "C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages" - ], - "configFilePaths": [ - "C:\\Users\\ms169\\AppData\\Roaming\\NuGet\\NuGet.Config", - "C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.FallbackLocation.config", - "C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.Offline.config" - ], - "originalTargetFrameworks": [ - "net9.0" - ], - "sources": { - "C:\\Program Files (x86)\\Microsoft SDKs\\NuGetPackages\\": {}, - "https://api.nuget.org/v3/index.json": {} - }, - "frameworks": { - "net9.0": { - "targetAlias": "net9.0", - "projectReferences": {} - } - }, - "warningProperties": { - "warnAsError": [ - "NU1605" - ] - }, - "restoreAuditProperties": { - "enableAudit": "true", - "auditLevel": "low", - "auditMode": "direct" - }, - "SdkAnalysisLevel": "9.0.200" - }, - "frameworks": { - "net9.0": { - "targetAlias": "net9.0", - "imports": [ - "net461", - "net462", - "net47", - "net471", - "net472", - "net48", - "net481" - ], - "assetTargetFallback": true, - "warn": true, - "frameworkReferences": { - "Microsoft.NETCore.App": { - "privateAssets": "all" - } - }, - "runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\9.0.201/PortableRuntimeIdentifierGraph.json" - } - } - } +{ + "version": 3, + "targets": { + "net8.0": {} + }, + "libraries": {}, + "projectFileDependencyGroups": { + "net8.0": [] + }, + "packageFolders": { + "C:\\Users\\asus\\.nuget\\packages\\": {}, + "C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages": {} + }, + "project": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "C:\\Users\\asus\\Documents\\CSharpHomework2025\\Homework\\Homework.csproj", + "projectName": "Homework", + "projectPath": "C:\\Users\\asus\\Documents\\CSharpHomework2025\\Homework\\Homework.csproj", + "packagesPath": "C:\\Users\\asus\\.nuget\\packages\\", + "outputPath": "C:\\Users\\asus\\Documents\\CSharpHomework2025\\Homework\\obj\\", + "projectStyle": "PackageReference", + "fallbackFolders": [ + "C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages" + ], + "configFilePaths": [ + "C:\\Users\\asus\\AppData\\Roaming\\NuGet\\NuGet.Config", + "C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.FallbackLocation.config", + "C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.Offline.config" + ], + "originalTargetFrameworks": [ + "net8.0" + ], + "sources": { + "C:\\Program Files (x86)\\Microsoft SDKs\\NuGetPackages\\": {}, + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net8.0": { + "targetAlias": "net8.0", + "projectReferences": {} + } + }, + "warningProperties": { + "warnAsError": [ + "NU1605" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "direct" + } + }, + "frameworks": { + "net8.0": { + "targetAlias": "net8.0", + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\8.0.400/PortableRuntimeIdentifierGraph.json" + } + } + } } \ No newline at end of file diff --git a/Homework/obj/project.nuget.cache b/Homework/obj/project.nuget.cache index d182034..216cd87 100644 --- a/Homework/obj/project.nuget.cache +++ b/Homework/obj/project.nuget.cache @@ -1,8 +1,8 @@ -{ - "version": 2, - "dgSpecHash": "Mpgpv19Q1hE=", - "success": true, - "projectFilePath": "C:\\Users\\ms169\\Desktop\\Homework\\Homework\\Homework.csproj", - "expectedPackageFiles": [], - "logs": [] +{ + "version": 2, + "dgSpecHash": "qCq5L6wGEBs=", + "success": true, + "projectFilePath": "C:\\Users\\asus\\Documents\\CSharpHomework2025\\Homework\\Homework.csproj", + "expectedPackageFiles": [], + "logs": [] } \ No newline at end of file diff --git a/dotnet-install.sh b/dotnet-install.sh new file mode 100644 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."