Add JDK installation support (Microsoft OpenJDK)#274
Merged
jonathanpeppers merged 24 commits intomainfrom Feb 26, 2026
Merged
Conversation
b807a5e to
7740819
Compare
7740819 to
938fdae
Compare
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
rmarinho
added a commit
that referenced
this pull request
Feb 24, 2026
- Fix GetStringAsync to use GetAsync + ReadAsStringAsync for cancellation support - Improve tar argument escaping using single quotes for paths with special chars - Implement rollback logic: restore previous JDK if move fails - Document .NET Standard 2.0 ZipFile cancellation limitation Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
rmarinho
added a commit
that referenced
this pull request
Feb 24, 2026
Contributor
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
Comments suppressed due to low confidence (1)
src/Xamarin.Android.Tools.AndroidSdk/JdkInstaller.cs:445
- When totalBytes is 0 (because both ContentLength and expectedSize are 0), the progress message will show "0 MB / 0 MB" which is acceptable, but the percentage calculation is skipped due to the check on line 443. However, if totalBytes is very small (less than 1 MB), the division by (1024 * 1024) will result in 0 for both values in the message, which could be confusing. Consider using KB or bytes for small files, or formatting the size more intelligently (e.g., using a helper method that chooses appropriate units).
if (progress != null && totalBytes > 0) {
var pct = (double) totalRead / totalBytes * 100;
progress.Report (new JdkInstallProgress (JdkInstallPhase.Downloading, pct, $"Downloaded {totalRead / (1024 * 1024)} MB / {totalBytes / (1024 * 1024)} MB"));
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
tests/Xamarin.Android.Tools.AndroidSdk-Tests/JdkInstallerTests.cs
Outdated
Show resolved
Hide resolved
This comment was marked as resolved.
This comment was marked as resolved.
Contributor
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 6 out of 6 changed files in this pull request and generated 8 comments.
Comments suppressed due to low confidence (2)
src/Xamarin.Android.Tools.AndroidSdk/JdkInstaller.cs:207
- InstallAsync treats an empty string targetPath the same as null and throws ArgumentNullException. For API clarity, consider throwing ArgumentException for empty/whitespace paths and reserving ArgumentNullException for null only (matching typical .NET conventions).
logger (TraceLevel.Error, $"JDK installation failed: {ex.Message}");
logger (TraceLevel.Verbose, ex.ToString ());
throw;
}
src/Xamarin.Android.Tools.AndroidSdk/JdkInstaller.cs:571
- Zip Slip validation uses StringComparison.OrdinalIgnoreCase unconditionally. On case-sensitive filesystems (Linux/macOS), a ZIP entry with an absolute path that differs only by case (e.g. "/TMP/..." vs root "/tmp/...") can incorrectly pass the StartsWith check, allowing extraction outside the destination directory. Use Ordinal on non-Windows (and ideally explicitly reject rooted entry.FullName) and keep OrdinalIgnoreCase only for Windows.
rmarinho
added a commit
that referenced
this pull request
Feb 24, 2026
- Fix GetStringAsync to use GetAsync + ReadAsStringAsync for cancellation support - Improve tar argument escaping using single quotes for paths with special chars - Implement rollback logic: restore previous JDK if move fails - Document .NET Standard 2.0 ZipFile cancellation limitation Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
4148670 to
03c994f
Compare
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- DiscoverAsync: log warning when checksum unavailable - InstallElevatedAsync: throw if checksum is null (matches InstallAsync behavior) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…Utils Move reusable methods to shared utility classes: - FileUtil: TryDeleteFile, TryDeleteDirectory, MoveWithRollback - DownloadUtils: FetchChecksumAsync JdkInstaller reduced from 546 to 464 lines. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Extracted IsTargetPathWritable, IsUnderDirectory, GetInstallerExtension, and GetArchiveExtension to FileUtil for reuse across the codebase. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Fix IsTargetPathWritable tests to use FileUtil static method - Remove redundant checksum null check (already enforced above) - Remove double blank line Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
tests/Xamarin.Android.Tools.AndroidSdk-Tests/JdkInstallerTests.cs
Outdated
Show resolved
Hide resolved
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- MoveWithRollback: defer backup deletion until after validation succeeds - Add CommitMove() to explicitly clean up backups post-validation - InstallAsync: clarify docs that elevated path ignores targetPath - Add dedicated OperationCanceledException catch (not logged as error) - DiscoverAsync test: add 15s CancellationToken timeout for CI reliability Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
6a66d2e to
1c1e8a1
Compare
This also uses `Environment.IsPrivilegedProcess`
Fix magic numbers
jonathanpeppers
approved these changes
Feb 26, 2026
Member
jonathanpeppers
left a comment
There was a problem hiding this comment.
I'm squashing/merging, getting a nice commit message.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds JDK installation support to
Xamarin.Android.Tools.AndroidSdkusing the Microsoft Build of OpenJDK (https://www.microsoft.com/openjdk).Closes #270
New Files
JdkInstaller.csDownloadUtils.csModels/JdkVersionInfo.csModels/JdkInstallPhase.csenumfor progress reporting phases (Downloading, Verifying, Extracting, etc.)Models/JdkInstallProgress.csrecordcombining phase, percentage, and message forIProgress<T>callbacksIsExternalInit.csinitaccessors on netstandard2.0.github/copilot-instructions.mdModified Files
FileUtil.csTryDeleteFile,TryDeleteDirectory,MoveWithRollback,CommitMove,IsTargetPathWritable,IsUnderDirectory,GetInstallerExtension,GetArchiveExtensionProcessUtils.csCreateProcessStartInfo(usesArgumentListon net5+,JoinArgumentsfallback),IsElevated(usesEnvironment.IsPrivilegedProcesson net7+, P/Invoke fallback).csprojSystem.Buffers4.5.1 (netstandard2.0),IsExternalInit.csexclusion on modern TFMsAPI Surface
Key Design Decisions
https://aka.ms/download-jdkwith SHA-256 checksum verificationRecommendedMajorVersion= 21,SupportedVersions=[21]. Array allows future additionsHttpClient— no injection;Dispose()cleans it upProcessUtils.IsElevated()is true, uses platform-native silent installers (.msiviamsiexec /qn,.pkgvia/usr/sbin/installer). When not elevated, extracts archive totargetPathMoveWithRollback: rename existing → temp, move new in place, delete temp viaCommitMove. Avoids Delete/Move race on WindowsArrayPool<byte>download buffer —ArrayPool<byte>.Shared.Rentfor the 80 KB download buffer, returned infinallyIsUserAnAdmin()(shell32) on Windows,geteuid()(libc) on Unix, behind#if !NET5_0_OR_GREATERNullProgresspattern — avoids null checks throughout install flownetstandard2.0+net10.0— conditional compilation forArgumentList,Environment.IsPrivilegedProcess,ReadAsStringAsync/ReadAsStreamAsyncoverloads. Private helpers encapsulate#ifblocksTests
66 unit tests across 5 test files (all target
net10.0):JdkInstallerTests.csIsValid,DiscoverAsync,InstallAsync,IsTargetPathWritable,Remove, propertiesDownloadUtilsTests.csParseChecksumFile(12 cases),VerifyChecksum(4),ExtractZipSafe+ Zip Slip (4)FileUtilTests.csMoveWithRollback(4),IsUnderDirectory(11 including cross-platform path cases)ProcessUtilsTests.csCreateProcessStartInfo(6),IsElevatedsmoke (1)JdkVersionInfoTests.csToString, mutable propertiesReview Feedback Addressed
JDK 21 only (removed 17), removed
HttpClientinjection,IDisposable,CancellationTokenpattern, path normalization, validation cleanup,ArgumentListfor tar, absolute/usr/bin/tar, null comparison pattern, one type per file,recordtype, checksum errors throw, testTearDowndispose, CI skip, elevated install mode, Zip Slip protection,ArrayPoolbuffer, extracted constants (BufferSize,BytesPerMB,WhitespaceChars), privateReadAsStreamAsync/ReadAsStringAsynchelpers, cross-platform test paths, comprehensive unit test coverageCo-authored-by: Copilot 223556219+Copilot@users.noreply.github.com