Skip to content

feat(#5): Auto-check .Net SDK installation#52

Open
ModerRAS wants to merge 1 commit intomasterfrom
feature/issue-5-sdk-checker
Open

feat(#5): Auto-check .Net SDK installation#52
ModerRAS wants to merge 1 commit intomasterfrom
feature/issue-5-sdk-checker

Conversation

@ModerRAS
Copy link
Copy Markdown
Contributor

Summary

  • Create ISdkCheckerService interface
  • Implement SdkCheckerService to detect SDK installation
  • Get installed SDK versions and paths
  • Provide download URL for .Net SDK
  • Add comprehensive unit tests

Changes

  • BannerlordModEditor.Common/Services/SdkCheckerService.cs - Implementation
  • BannerlordModEditor.Common.Tests/Services/SdkCheckerServiceTests.cs - Unit tests

Closes #5

- Create ISdkCheckerService interface
- Implement SdkCheckerService to detect SDK installation
- Get installed SDK versions and paths
- Provide download URL for .Net SDK
- Add comprehensive unit tests

Closes #5
Copilot AI review requested due to automatic review settings March 20, 2026 01:20
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a service for detecting whether the .NET SDK is installed, reporting installed versions/path, and exposing a recommended SDK download URL (issue #5), along with initial tests.

Changes:

  • Introduced ISdkCheckerService and SdkCheckerService to query dotnet via CLI.
  • Added SdkInfo DTO carrying install/version/path/version-list plus recommended version and download URL.
  • Added xUnit tests covering basic “non-null/returns” behavior for the new service.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 6 comments.

File Description
BannerlordModEditor.Common/Services/SdkCheckerService.cs Implements SDK detection by invoking dotnet commands and parsing their output into SdkInfo.
BannerlordModEditor.Common.Tests/Services/SdkCheckerServiceTests.cs Adds baseline tests for service methods and returned DTO fields.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +36 to +38
public async Task<SdkInfo> CheckSdkAsync()
{
return await Task.Run(() => CheckSdk());
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CheckSdkAsync wraps a synchronous, blocking implementation in Task.Run, which is generally discouraged for library/service code (it consumes a thread-pool thread and doesn’t make the underlying work non-blocking). Prefer either (a) making the underlying process execution truly async (e.g., async read + WaitForExitAsync) or (b) removing the async method and keeping only CheckSdk() if true async isn’t feasible.

Suggested change
public async Task<SdkInfo> CheckSdkAsync()
{
return await Task.Run(() => CheckSdk());
public Task<SdkInfo> CheckSdkAsync()
{
return Task.FromResult(CheckSdk());

Copilot uses AI. Check for mistakes.
Comment on lines +74 to +76
var output = process.StandardOutput.ReadToEnd();
process.WaitForExit();

Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can deadlock/hang: RedirectStandardError = true but stderr is never drained. If dotnet writes enough to stderr, the child process can block, causing ReadToEnd()/WaitForExit() to hang indefinitely. Fix by either reading stderr as well (or not redirecting it), and consider using WaitForExitAsync plus async reads to avoid blocking.

Suggested change
var output = process.StandardOutput.ReadToEnd();
process.WaitForExit();
var stdoutTask = process.StandardOutput.ReadToEndAsync();
var stderrTask = process.StandardError.ReadToEndAsync();
process.WaitForExit();
Task.WaitAll(stdoutTask, stderrTask);
var output = stdoutTask.Result;

Copilot uses AI. Check for mistakes.
Comment on lines +89 to +103
var process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = "dotnet",
Arguments = "--version",
RedirectStandardOutput = true,
UseShellExecute = false,
CreateNoWindow = true
}
};

process.Start();
var output = process.StandardOutput.ReadToEnd().Trim();
process.WaitForExit();
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All process invocations use blocking ReadToEnd() + WaitForExit() without any timeout/cancellation. In environments where dotnet is slow/hung/misconfigured, this can block the calling thread (and CheckSdkAsync will still tie up a thread-pool thread). Consider adding a bounded timeout (and/or cancellation token support) and cleaning up the process if it exceeds the limit.

Copilot uses AI. Check for mistakes.
Comment on lines +137 to +144
foreach (var line in output.Split('\n', StringSplitOptions.RemoveEmptyEntries))
{
var parts = line.Trim().Split(' ');
if (parts.Length > 0)
{
versions.Add(parts[0]);
}
}
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Parsing uses Split(' ') without removing empty entries. If the --list-sdks output contains multiple spaces/tabs, parts[0] can be empty, producing empty version strings in versions. Use a split that removes empty entries (and preferably handles tabs) before taking the first token.

Copilot uses AI. Check for mistakes.
Comment on lines +56 to +77
public bool IsSdkInstalled()
{
try
{
var process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = "dotnet",
Arguments = "--version",
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = true
}
};

process.Start();
var output = process.StandardOutput.ReadToEnd();
process.WaitForExit();

return process.ExitCode == 0 && !string.IsNullOrWhiteSpace(output);
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Process-start logic is duplicated across IsSdkInstalled, GetSdkVersion, GetInstalledVersions, and GetSdkPath, making it harder to consistently apply fixes (timeouts, stderr draining, disposal, etc.). Consider extracting a single helper (e.g., RunDotnet(args) returning exit code + stdout/stderr) and reuse it for all commands.

Suggested change
public bool IsSdkInstalled()
{
try
{
var process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = "dotnet",
Arguments = "--version",
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = true
}
};
process.Start();
var output = process.StandardOutput.ReadToEnd();
process.WaitForExit();
return process.ExitCode == 0 && !string.IsNullOrWhiteSpace(output);
private (int ExitCode, string StdOut, string StdErr) RunDotnet(string arguments)
{
var startInfo = new ProcessStartInfo
{
FileName = "dotnet",
Arguments = arguments,
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = true
};
using var process = new Process
{
StartInfo = startInfo
};
process.Start();
string stdout = process.StandardOutput.ReadToEnd();
string stderr = process.StandardError.ReadToEnd();
process.WaitForExit();
return (process.ExitCode, stdout, stderr);
}
public bool IsSdkInstalled()
{
try
{
var result = RunDotnet("--version");
return result.ExitCode == 0 && !string.IsNullOrWhiteSpace(result.StdOut);

Copilot uses AI. Check for mistakes.
Comment on lines +26 to +33
public async Task CheckSdkAsync_ReturnsValidInfo()
{
var info = await _service.CheckSdkAsync();

Assert.NotNull(info);
Assert.NotNull(info.RecommendedVersion);
Assert.NotNull(info.DownloadUrl);
}
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These tests execute the real dotnet CLI on the machine running the tests. That makes them environment-dependent and potentially flaky in CI (e.g., missing SDK, different PATH, localized output). To make them true unit tests, introduce an abstraction for process execution (or inject a runner) and mock outputs to deterministically test parsing/version detection/path extraction; then add tests for --list-sdks parsing and GetSdkPath() behavior.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature] 自动下载 .Net SDK

2 participants