-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Respond to dnup feedback from prototype code review
#51446
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
nagilson
wants to merge
20
commits into
dotnet:release/dnup
Choose a base branch
from
nagilson:nagilson-prototype-code-rv
base: release/dnup
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from all commits
Commits
Show all changes
20 commits
Select commit
Hold shift + click to select a range
bee1fdd
Respond to `dnup` feedback from prototype code review
nagilson 67ab374
Fix build issue
nagilson 2b64719
Refactor our channel to release version parsing logic
nagilson 7ae5d2e
Fix bad paran
nagilson 6a1457c
Skip extra hostfxr logic
nagilson 330aa7f
Use hostfxr API to get .NET SDKs that are available to that dotnet root
nagilson 189f4e6
Add reference to native wrapper for hostfxr
nagilson 73b6023
Use the same release index so we don't download it twice
nagilson 34489cc
Delete unneeded sln which is replaced by slnf
nagilson 3076c56
Fix library call
nagilson 79b81c2
Remove extra function
nagilson b87c196
Use same naming convention as other classes
nagilson c996289
Simplify archive extraction, remark on bad hostfxr logic, fix test
nagilson 5c5a00d
remove unused function
nagilson de66740
Implement wrapper around hostfxr calls
nagilson 5b4acf3
Merge branch 'dnup' into nagilson-prototype-code-rv
nagilson 1942ea5
Merge branch 'release/dnup' into nagilson-prototype-code-rv
nagilson e748293
Merge remote-tracking branch 'upstream/release/dnup' into nagilson-pr…
nagilson 90bfe48
add methods from merge
nagilson 30bfd67
Fix merge issues
nagilson File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
270 changes: 270 additions & 0 deletions
270
src/Installer/Microsoft.Dotnet.Installation/Internal/ChannelVersionResolver.cs
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,270 @@ | ||
| // Licensed to the .NET Foundation under one or more agreements. | ||
| // The .NET Foundation licenses this file to you under the MIT license. | ||
|
|
||
| using System.Collections.Generic; | ||
| using System.Linq; | ||
| using Microsoft.Deployment.DotNet.Releases; | ||
|
|
||
|
|
||
| namespace Microsoft.Dotnet.Installation.Internal; | ||
|
|
||
| internal class ChannelVersionResolver | ||
| { | ||
| private ReleaseManifest _releaseManifest = new(); | ||
|
|
||
| public ChannelVersionResolver() | ||
| { | ||
|
|
||
| } | ||
|
|
||
| public ChannelVersionResolver(ReleaseManifest releaseManifest) | ||
| { | ||
| _releaseManifest = releaseManifest; | ||
| } | ||
|
|
||
| public ReleaseVersion? Resolve(DotnetInstallRequest installRequest) | ||
| { | ||
| return GetLatestVersionForChannel(installRequest.Channel, installRequest.Component); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Parses a version channel string into its components. | ||
| /// </summary> | ||
| /// <param name="channel">Channel string to parse (e.g., "9", "9.0", "9.0.1xx", "9.0.103")</param> | ||
| /// <returns>Tuple containing (major, minor, featureBand, isFullySpecified)</returns> | ||
| private (int Major, int Minor, string? FeatureBand, bool IsFullySpecified) ParseVersionChannel(UpdateChannel channel) | ||
| { | ||
| var parts = channel.Name.Split('.'); | ||
| int major = parts.Length > 0 && int.TryParse(parts[0], out var m) ? m : -1; | ||
| int minor = parts.Length > 1 && int.TryParse(parts[1], out var n) ? n : -1; | ||
|
|
||
| // Check if we have a feature band (like 1xx) or a fully specified patch | ||
| string? featureBand = null; | ||
| bool isFullySpecified = false; | ||
|
|
||
| if (parts.Length >= 3) | ||
| { | ||
| if (parts[2].EndsWith("xx")) | ||
| { | ||
| // Feature band pattern (e.g., "1xx") | ||
| featureBand = parts[2].Substring(0, parts[2].Length - 2); | ||
| } | ||
| else if (int.TryParse(parts[2], out _)) | ||
| { | ||
| // Fully specified version (e.g., "9.0.103") | ||
| isFullySpecified = true; | ||
| } | ||
| } | ||
|
|
||
| return (major, minor, featureBand, isFullySpecified); | ||
| } | ||
|
|
||
| public IEnumerable<string> GetSupportedChannels() | ||
| { | ||
|
|
||
| return ["latest", "preview", "lts", "sts", | ||
| .._releaseManifest.GetReleasesIndex() | ||
| .Where(p => p.IsSupported) | ||
| .OrderByDescending(p => p.LatestReleaseVersion) | ||
| .SelectMany(GetChannelsForProduct) | ||
| ]; | ||
|
|
||
| static IEnumerable<string> GetChannelsForProduct(Product product) | ||
| { | ||
| return [product.ProductVersion, | ||
| ..product.GetReleasesAsync().GetAwaiter().GetResult() | ||
| .SelectMany(r => r.Sdks) | ||
| .Select(sdk => sdk.Version) | ||
| .OrderByDescending(v => v) | ||
| .Select(v => $"{v.Major}.{v.Minor}.{(v.Patch / 100)}xx") | ||
| .Distinct() | ||
| .ToList() | ||
| ]; | ||
| } | ||
|
|
||
| } | ||
|
|
||
| /// <summary> | ||
| /// Finds the latest fully specified version for a given channel string (major, major.minor, or feature band). | ||
| /// </summary> | ||
| /// <param name="channel">Channel string (e.g., "9", "9.0", "9.0.1xx", "9.0.103", "lts", "sts", "preview")</param> | ||
| /// <param name="mode">InstallMode.SDK or InstallMode.Runtime</param> | ||
| /// <returns>Latest fully specified version string, or null if not found</returns> | ||
| public ReleaseVersion? GetLatestVersionForChannel(UpdateChannel channel, InstallComponent component) | ||
| { | ||
| if (string.Equals(channel.Name, "lts", StringComparison.OrdinalIgnoreCase) || string.Equals(channel.Name, "sts", StringComparison.OrdinalIgnoreCase)) | ||
| { | ||
| var releaseType = string.Equals(channel.Name, "lts", StringComparison.OrdinalIgnoreCase) ? ReleaseType.LTS : ReleaseType.STS; | ||
| var productIndex = _releaseManifest.GetReleasesIndex(); | ||
| return GetLatestVersionByReleaseType(productIndex, releaseType, component); | ||
| } | ||
| else if (string.Equals(channel.Name, "preview", StringComparison.OrdinalIgnoreCase)) | ||
| { | ||
| var productIndex = _releaseManifest.GetReleasesIndex(); | ||
| return GetLatestPreviewVersion(productIndex, component); | ||
| } | ||
| else if (string.Equals(channel.Name, "latest", StringComparison.OrdinalIgnoreCase)) | ||
| { | ||
| var productIndex = _releaseManifest.GetReleasesIndex(); | ||
| return GetLatestActiveVersion(productIndex, component); | ||
| } | ||
|
|
||
| var (major, minor, featureBand, isFullySpecified) = ParseVersionChannel(channel); | ||
|
|
||
| // If major is invalid, return null | ||
| if (major < 0) | ||
| { | ||
| return null; | ||
| } | ||
|
|
||
| // If the version is already fully specified, just return it as-is | ||
| if (isFullySpecified) | ||
| { | ||
| return new ReleaseVersion(channel.Name); | ||
| } | ||
|
|
||
| // Load the index manifest | ||
| var index = _releaseManifest.GetReleasesIndex(); | ||
| if (minor < 0) | ||
| { | ||
| return GetLatestVersionForMajorOrMajorMinor(index, major, component); // Major Only (e.g., "9") | ||
| } | ||
| else if (minor >= 0 && featureBand == null) // Major.Minor (e.g., "9.0") | ||
| { | ||
| return GetLatestVersionForMajorOrMajorMinor(index, major, component, minor); | ||
| } | ||
| else if (minor >= 0 && featureBand is not null) // Not Fully Qualified Feature band Version (e.g., "9.0.1xx") | ||
| { | ||
| return GetLatestVersionForFeatureBand(index, major, minor, featureBand, component); | ||
| } | ||
|
|
||
| return null; | ||
| } | ||
|
|
||
| private IEnumerable<Product> GetProductsInMajorOrMajorMinor(IEnumerable<Product> index, int major, int? minor = null) | ||
| { | ||
| var validProducts = index.Where(p => p.ProductVersion.StartsWith(minor is not null ? $"{major}.{minor}" : $"{major}.")); | ||
| return validProducts; | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Gets the latest version for a major-only channel (e.g., "9"). | ||
| /// </summary> | ||
| private ReleaseVersion? GetLatestVersionForMajorOrMajorMinor(IEnumerable<Product> index, int major, InstallComponent component, int? minor = null) | ||
| { | ||
| // Assumption: The manifest is designed so that the first product for a major version will always be latest. | ||
| Product? latestProductWithMajor = GetProductsInMajorOrMajorMinor(index, major, minor).FirstOrDefault(); | ||
| return GetLatestReleaseVersionInProduct(latestProductWithMajor, component); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Gets the latest version based on support status (LTS or STS). | ||
| /// </summary> | ||
| /// <param name="index">The product collection to search</param> | ||
| /// <param name="isLts">True for LTS (Long-Term Support), false for STS (Standard-Term Support)</param> | ||
| /// <param name="mode">InstallComponent.SDK or InstallComponent.Runtime</param> | ||
|
Comment on lines
+164
to
+165
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Looks like these doc comments are out of date. |
||
| /// <returns>Latest stable version string matching the support status, or null if none found</returns> | ||
| private static ReleaseVersion? GetLatestVersionByReleaseType(IEnumerable<Product> index, ReleaseType releaseType, InstallComponent component) | ||
| { | ||
| var correctPhaseProducts = index?.Where(p => p.ReleaseType == releaseType) ?? Enumerable.Empty<Product>(); | ||
| return GetLatestActiveVersion(correctPhaseProducts, component); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Gets the latest preview version available. | ||
| /// </summary> | ||
| /// <param name="index">The product collection to search</param> | ||
| /// <param name="mode">InstallComponent.SDK or InstallComponent.Runtime</param> | ||
| /// <returns>Latest preview or GoLive version string, or null if none found</returns> | ||
| private ReleaseVersion? GetLatestPreviewVersion(IEnumerable<Product> index, InstallComponent component) | ||
| { | ||
| ReleaseVersion? latestPreviewVersion = GetLatestVersionBySupportPhase(index, component, [SupportPhase.Preview, SupportPhase.GoLive]); | ||
| if (latestPreviewVersion is not null) | ||
| { | ||
| return latestPreviewVersion; | ||
| } | ||
|
|
||
| return GetLatestVersionBySupportPhase(index, component, [SupportPhase.Active]); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Gets the latest version across all available products that matches the support phase. | ||
| /// </summary> | ||
| private static ReleaseVersion? GetLatestActiveVersion(IEnumerable<Product> index, InstallComponent component) | ||
| { | ||
| return GetLatestVersionBySupportPhase(index, component, [SupportPhase.Active]); | ||
| } | ||
| /// <summary> | ||
| /// Gets the latest version across all available products that matches the support phase. | ||
| /// </summary> | ||
| private static ReleaseVersion? GetLatestVersionBySupportPhase(IEnumerable<Product> index, InstallComponent component, SupportPhase[] acceptedSupportPhases) | ||
| { | ||
| // A version in preview/ga/rtm support is considered Go Live and not Active. | ||
| var activeSupportProducts = index?.Where(p => acceptedSupportPhases.Contains(p.SupportPhase)); | ||
|
|
||
| // The manifest is designed so that the first product will always be latest. | ||
| Product? latestActiveSupportProduct = activeSupportProducts?.FirstOrDefault(); | ||
|
|
||
| return GetLatestReleaseVersionInProduct(latestActiveSupportProduct, component); | ||
| } | ||
|
|
||
| private static ReleaseVersion? GetLatestReleaseVersionInProduct(Product? product, InstallComponent component) | ||
| { | ||
| // Assumption: The latest runtime version will always be the same across runtime components. | ||
| ReleaseVersion? latestVersion = component switch | ||
| { | ||
| InstallComponent.SDK => product?.LatestSdkVersion, | ||
| _ => product?.LatestRuntimeVersion | ||
| }; | ||
|
|
||
| return latestVersion; | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Replaces user input feature band strings into the full feature band. | ||
| /// This would convert '1xx' into '100'. | ||
| /// 100 is not necessarily the latest but it is the feature band. | ||
| /// The other number in the band is the patch. | ||
| /// </summary> | ||
| /// <param name="band"></param> | ||
| /// <returns></returns> | ||
| private static int NormalizeFeatureBandInput(string band) | ||
| { | ||
| var bandString = band | ||
| .Replace("X", "x") | ||
| .Replace("x", "0") | ||
| .PadRight(3, '0') | ||
| .Substring(0, 3); | ||
| return int.Parse(bandString); | ||
| } | ||
|
|
||
|
|
||
| /// <summary> | ||
| /// Gets the latest version for a feature band channel (e.g., "9.0.1xx"). | ||
| /// </summary> | ||
| private ReleaseVersion? GetLatestVersionForFeatureBand(ProductCollection index, int major, int minor, string featureBand, InstallComponent component) | ||
| { | ||
| if (component != InstallComponent.SDK) | ||
| { | ||
| return null; | ||
| } | ||
|
|
||
| var validProducts = GetProductsInMajorOrMajorMinor(index, major, minor); | ||
| var latestProduct = validProducts.FirstOrDefault(); | ||
| var releases = latestProduct?.GetReleasesAsync().GetAwaiter().GetResult().ToList() ?? []; | ||
| var normalizedFeatureBand = NormalizeFeatureBandInput(featureBand); | ||
|
|
||
| foreach (var release in releases) | ||
| { | ||
| foreach (var sdk in release.Sdks) | ||
| { | ||
| if (sdk.Version.SdkFeatureBand == normalizedFeatureBand) | ||
| { | ||
| return sdk.Version; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| return null; | ||
| } | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we do this by checking a version object instead of checking if the string starts with something? I think we'll need to parse the version first but that would seem better to me.