From 71777a1606e63fb07103c4b82ce1e88403e3859e Mon Sep 17 00:00:00 2001 From: Eduardo Villalpando Mello Date: Wed, 16 Apr 2025 16:43:37 -0700 Subject: [PATCH 01/20] Remove LocalizableStrings.Designer.cs --- .../LocalizableStrings.Designer.cs | 72 ------------------- 1 file changed, 72 deletions(-) delete mode 100644 src/dotnet-bootstrapper/LocalizableStrings.Designer.cs diff --git a/src/dotnet-bootstrapper/LocalizableStrings.Designer.cs b/src/dotnet-bootstrapper/LocalizableStrings.Designer.cs deleted file mode 100644 index 8ff3e4a7..00000000 --- a/src/dotnet-bootstrapper/LocalizableStrings.Designer.cs +++ /dev/null @@ -1,72 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace Microsoft.DotNet.Tools.Uninstall -{ - using System; - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class LocalizableStrings - { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal LocalizableStrings() - { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager - { - get - { - if (object.ReferenceEquals(resourceMan, null)) - { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.DotNet.Tools.Uninstall.LocalizableStrings", typeof(LocalizableStrings).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture - { - get - { - return resourceCulture; - } - set - { - resourceCulture = value; - } - } - } -} From 8c10e2dfea2a9572ca96ab16092bb399f0fee179 Mon Sep 17 00:00:00 2001 From: Eduardo Villalpando Mello Date: Wed, 16 Apr 2025 17:07:41 -0700 Subject: [PATCH 02/20] Use System.CommandLine --- src/dotnet-bootstrapper/Program.cs | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/src/dotnet-bootstrapper/Program.cs b/src/dotnet-bootstrapper/Program.cs index e04cb49d..04144fa5 100644 --- a/src/dotnet-bootstrapper/Program.cs +++ b/src/dotnet-bootstrapper/Program.cs @@ -1,13 +1,30 @@ // 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. -namespace Microsoft.DotNet.Tools.Bootstrapper +using System.CommandLine; +using System.CommandLine.Builder; +using System.CommandLine.Parsing; +using System.IO; +using System.Threading.Tasks; +namespace Microsoft.DotNet.Tools.Bootstrapper; + +class Program { - internal class Program + static int Main(string[] args) { - internal static int Main(string[] args) + var rootCommand = new RootCommand("dotnet bootstrapper"); + + var installCommand = new Command("install", "Install the specified version of the .NET SDK.") { - return 0; - } + new Argument("version") + { + Description = "The version of the .NET SDK to install.", + Arity = ArgumentArity.ExactlyOne + } + }; + + rootCommand.AddCommand(installCommand); + + return rootCommand.Invoke(args); } } From ec9633622ac3bcd5289f4c07ace302ebbdee1b64 Mon Sep 17 00:00:00 2001 From: Forgind <12969783+Forgind@users.noreply.github.com> Date: Mon, 21 Apr 2025 13:37:36 -0700 Subject: [PATCH 03/20] Add real command parser --- .../BootstrapperCommandParser.cs | 50 +++++++++++++++++++ src/dotnet-bootstrapper/Program.cs | 15 +----- .../EndToEndTests.cs | 3 +- 3 files changed, 53 insertions(+), 15 deletions(-) create mode 100644 src/dotnet-bootstrapper/BootstrapperCommandParser.cs diff --git a/src/dotnet-bootstrapper/BootstrapperCommandParser.cs b/src/dotnet-bootstrapper/BootstrapperCommandParser.cs new file mode 100644 index 00000000..e754a6ae --- /dev/null +++ b/src/dotnet-bootstrapper/BootstrapperCommandParser.cs @@ -0,0 +1,50 @@ +// 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. + +using System; +using System.CommandLine; +using System.CommandLine.Builder; +using System.CommandLine.Invocation; +using System.CommandLine.Parsing; +using System.Reflection; + +namespace Microsoft.DotNet.Tools.Bootstrapper +{ + internal static class BootstrapperCommandParser + { + public static Parser BootstrapParser; + + public static RootCommand BootstrapperRootCommand = new RootCommand("dotnet bootstrapper"); + + public static readonly Command VersionCommand = new Command("--version"); + + private static readonly Lazy _assemblyVersion = + new Lazy(() => + { + var assembly = Assembly.GetEntryAssembly() ?? Assembly.GetExecutingAssembly(); + var assemblyVersionAttribute = assembly.GetCustomAttribute(); + if (assemblyVersionAttribute == null) + { + return assembly.GetName().Version.ToString(); + } + else + { + return assemblyVersionAttribute.InformationalVersion; + } + }); + + static BootstrapperCommandParser() + { + BootstrapperRootCommand.AddCommand(VersionCommand); + VersionCommand.Handler = CommandHandler.Create(() => + { + Console.WriteLine(_assemblyVersion.Value); + }); + + BootstrapParser = new CommandLineBuilder(BootstrapperRootCommand) + .UseDefaults() + // .UseHelpBuilder(context => new UninstallHelpBuilder(context.Console)) + .Build(); + } + } +} diff --git a/src/dotnet-bootstrapper/Program.cs b/src/dotnet-bootstrapper/Program.cs index 04144fa5..9985c164 100644 --- a/src/dotnet-bootstrapper/Program.cs +++ b/src/dotnet-bootstrapper/Program.cs @@ -12,19 +12,6 @@ class Program { static int Main(string[] args) { - var rootCommand = new RootCommand("dotnet bootstrapper"); - - var installCommand = new Command("install", "Install the specified version of the .NET SDK.") - { - new Argument("version") - { - Description = "The version of the .NET SDK to install.", - Arity = ArgumentArity.ExactlyOne - } - }; - - rootCommand.AddCommand(installCommand); - - return rootCommand.Invoke(args); + return BootstrapperCommandParser.BootstrapParser.InvokeAsync(args).Result; } } diff --git a/test/dotnet-bootstrapper.Tests/EndToEndTests.cs b/test/dotnet-bootstrapper.Tests/EndToEndTests.cs index b80d63b7..5b0456ac 100644 --- a/test/dotnet-bootstrapper.Tests/EndToEndTests.cs +++ b/test/dotnet-bootstrapper.Tests/EndToEndTests.cs @@ -31,7 +31,8 @@ internal void ItReturnsZeroOnExit() RedirectStandardOutput = true, RedirectStandardError = true, UseShellExecute = false, - CreateNoWindow = true + CreateNoWindow = true, + Arguments = "--version" } }; From 5ca0b5c6e74b6a3835d10570db3a6d478821db23 Mon Sep 17 00:00:00 2001 From: Forgind <12969783+Forgind@users.noreply.github.com> Date: Mon, 21 Apr 2025 16:11:06 -0700 Subject: [PATCH 04/20] Try using uninstall commands --- .../BootstrapperCommandParser.cs | 26 +++++-------------- .../LocalizableStrings.resx | 3 +++ .../dotnet-bootstrapper.csproj | 1 + .../xlf/LocalizableStrings.cs.xlf | 8 +++++- .../xlf/LocalizableStrings.de.xlf | 8 +++++- .../xlf/LocalizableStrings.es.xlf | 8 +++++- .../xlf/LocalizableStrings.fr.xlf | 8 +++++- .../xlf/LocalizableStrings.it.xlf | 8 +++++- .../xlf/LocalizableStrings.ja.xlf | 8 +++++- .../xlf/LocalizableStrings.ko.xlf | 8 +++++- .../xlf/LocalizableStrings.pl.xlf | 8 +++++- .../xlf/LocalizableStrings.pt-BR.xlf | 8 +++++- .../xlf/LocalizableStrings.ru.xlf | 8 +++++- .../xlf/LocalizableStrings.tr.xlf | 8 +++++- .../xlf/LocalizableStrings.zh-Hans.xlf | 8 +++++- .../xlf/LocalizableStrings.zh-Hant.xlf | 8 +++++- .../Shared/Configs/CommandLineConfigs.cs | 2 ++ .../dotnet-core-uninstall.csproj | 1 + 18 files changed, 105 insertions(+), 32 deletions(-) diff --git a/src/dotnet-bootstrapper/BootstrapperCommandParser.cs b/src/dotnet-bootstrapper/BootstrapperCommandParser.cs index e754a6ae..ef6b8137 100644 --- a/src/dotnet-bootstrapper/BootstrapperCommandParser.cs +++ b/src/dotnet-bootstrapper/BootstrapperCommandParser.cs @@ -7,6 +7,7 @@ using System.CommandLine.Invocation; using System.CommandLine.Parsing; using System.Reflection; +using Microsoft.DotNet.Tools.Uninstall.Shared.Configs; namespace Microsoft.DotNet.Tools.Bootstrapper { @@ -15,30 +16,17 @@ internal static class BootstrapperCommandParser public static Parser BootstrapParser; public static RootCommand BootstrapperRootCommand = new RootCommand("dotnet bootstrapper"); - - public static readonly Command VersionCommand = new Command("--version"); - - private static readonly Lazy _assemblyVersion = - new Lazy(() => - { - var assembly = Assembly.GetEntryAssembly() ?? Assembly.GetExecutingAssembly(); - var assemblyVersionAttribute = assembly.GetCustomAttribute(); - if (assemblyVersionAttribute == null) - { - return assembly.GetName().Version.ToString(); - } - else - { - return assemblyVersionAttribute.InformationalVersion; - } - }); + public static readonly Command HelpCommand = new("--help"); static BootstrapperCommandParser() { - BootstrapperRootCommand.AddCommand(VersionCommand); + BootstrapperRootCommand.AddCommand(CommandLineConfigs.VersionSubcommand); + BootstrapperRootCommand.AddCommand(CommandLineConfigs.ListCommand); + BootstrapperRootCommand.AddCommand(CommandLineConfigs.RemoveCommand); + BootstrapperRootCommand.AddCommand(HelpCommand); VersionCommand.Handler = CommandHandler.Create(() => { - Console.WriteLine(_assemblyVersion.Value); + Console.WriteLine(LocalizableStrings.BootstrapperHelp); }); BootstrapParser = new CommandLineBuilder(BootstrapperRootCommand) diff --git a/src/dotnet-bootstrapper/LocalizableStrings.resx b/src/dotnet-bootstrapper/LocalizableStrings.resx index 8b2ff64a..85e4edd0 100644 --- a/src/dotnet-bootstrapper/LocalizableStrings.resx +++ b/src/dotnet-bootstrapper/LocalizableStrings.resx @@ -117,4 +117,7 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + Bootstrapper help text + \ No newline at end of file diff --git a/src/dotnet-bootstrapper/dotnet-bootstrapper.csproj b/src/dotnet-bootstrapper/dotnet-bootstrapper.csproj index 3c081deb..1f570479 100644 --- a/src/dotnet-bootstrapper/dotnet-bootstrapper.csproj +++ b/src/dotnet-bootstrapper/dotnet-bootstrapper.csproj @@ -25,6 +25,7 @@ + diff --git a/src/dotnet-bootstrapper/xlf/LocalizableStrings.cs.xlf b/src/dotnet-bootstrapper/xlf/LocalizableStrings.cs.xlf index e028ecc8..62e39f34 100644 --- a/src/dotnet-bootstrapper/xlf/LocalizableStrings.cs.xlf +++ b/src/dotnet-bootstrapper/xlf/LocalizableStrings.cs.xlf @@ -1,6 +1,12 @@  - + + + Bootstrapper help text + Bootstrapper help text + + + \ No newline at end of file diff --git a/src/dotnet-bootstrapper/xlf/LocalizableStrings.de.xlf b/src/dotnet-bootstrapper/xlf/LocalizableStrings.de.xlf index 02f57c3c..b900a85c 100644 --- a/src/dotnet-bootstrapper/xlf/LocalizableStrings.de.xlf +++ b/src/dotnet-bootstrapper/xlf/LocalizableStrings.de.xlf @@ -1,6 +1,12 @@  - + + + Bootstrapper help text + Bootstrapper help text + + + \ No newline at end of file diff --git a/src/dotnet-bootstrapper/xlf/LocalizableStrings.es.xlf b/src/dotnet-bootstrapper/xlf/LocalizableStrings.es.xlf index bd51c90f..1bdf68b3 100644 --- a/src/dotnet-bootstrapper/xlf/LocalizableStrings.es.xlf +++ b/src/dotnet-bootstrapper/xlf/LocalizableStrings.es.xlf @@ -1,6 +1,12 @@  - + + + Bootstrapper help text + Bootstrapper help text + + + \ No newline at end of file diff --git a/src/dotnet-bootstrapper/xlf/LocalizableStrings.fr.xlf b/src/dotnet-bootstrapper/xlf/LocalizableStrings.fr.xlf index 353aa168..3faf4859 100644 --- a/src/dotnet-bootstrapper/xlf/LocalizableStrings.fr.xlf +++ b/src/dotnet-bootstrapper/xlf/LocalizableStrings.fr.xlf @@ -1,6 +1,12 @@  - + + + Bootstrapper help text + Bootstrapper help text + + + \ No newline at end of file diff --git a/src/dotnet-bootstrapper/xlf/LocalizableStrings.it.xlf b/src/dotnet-bootstrapper/xlf/LocalizableStrings.it.xlf index 8521fc88..0fe72bf7 100644 --- a/src/dotnet-bootstrapper/xlf/LocalizableStrings.it.xlf +++ b/src/dotnet-bootstrapper/xlf/LocalizableStrings.it.xlf @@ -1,6 +1,12 @@  - + + + Bootstrapper help text + Bootstrapper help text + + + \ No newline at end of file diff --git a/src/dotnet-bootstrapper/xlf/LocalizableStrings.ja.xlf b/src/dotnet-bootstrapper/xlf/LocalizableStrings.ja.xlf index a4bdbd56..b99ec495 100644 --- a/src/dotnet-bootstrapper/xlf/LocalizableStrings.ja.xlf +++ b/src/dotnet-bootstrapper/xlf/LocalizableStrings.ja.xlf @@ -1,6 +1,12 @@  - + + + Bootstrapper help text + Bootstrapper help text + + + \ No newline at end of file diff --git a/src/dotnet-bootstrapper/xlf/LocalizableStrings.ko.xlf b/src/dotnet-bootstrapper/xlf/LocalizableStrings.ko.xlf index dfe7ff8c..5291085c 100644 --- a/src/dotnet-bootstrapper/xlf/LocalizableStrings.ko.xlf +++ b/src/dotnet-bootstrapper/xlf/LocalizableStrings.ko.xlf @@ -1,6 +1,12 @@  - + + + Bootstrapper help text + Bootstrapper help text + + + \ No newline at end of file diff --git a/src/dotnet-bootstrapper/xlf/LocalizableStrings.pl.xlf b/src/dotnet-bootstrapper/xlf/LocalizableStrings.pl.xlf index edb8d7de..5fb33884 100644 --- a/src/dotnet-bootstrapper/xlf/LocalizableStrings.pl.xlf +++ b/src/dotnet-bootstrapper/xlf/LocalizableStrings.pl.xlf @@ -1,6 +1,12 @@  - + + + Bootstrapper help text + Bootstrapper help text + + + \ No newline at end of file diff --git a/src/dotnet-bootstrapper/xlf/LocalizableStrings.pt-BR.xlf b/src/dotnet-bootstrapper/xlf/LocalizableStrings.pt-BR.xlf index 616bbc89..5275a2d0 100644 --- a/src/dotnet-bootstrapper/xlf/LocalizableStrings.pt-BR.xlf +++ b/src/dotnet-bootstrapper/xlf/LocalizableStrings.pt-BR.xlf @@ -1,6 +1,12 @@  - + + + Bootstrapper help text + Bootstrapper help text + + + \ No newline at end of file diff --git a/src/dotnet-bootstrapper/xlf/LocalizableStrings.ru.xlf b/src/dotnet-bootstrapper/xlf/LocalizableStrings.ru.xlf index 8e47a5f3..f8ddcba8 100644 --- a/src/dotnet-bootstrapper/xlf/LocalizableStrings.ru.xlf +++ b/src/dotnet-bootstrapper/xlf/LocalizableStrings.ru.xlf @@ -1,6 +1,12 @@  - + + + Bootstrapper help text + Bootstrapper help text + + + \ No newline at end of file diff --git a/src/dotnet-bootstrapper/xlf/LocalizableStrings.tr.xlf b/src/dotnet-bootstrapper/xlf/LocalizableStrings.tr.xlf index 770cdf4a..d4074d68 100644 --- a/src/dotnet-bootstrapper/xlf/LocalizableStrings.tr.xlf +++ b/src/dotnet-bootstrapper/xlf/LocalizableStrings.tr.xlf @@ -1,6 +1,12 @@  - + + + Bootstrapper help text + Bootstrapper help text + + + \ No newline at end of file diff --git a/src/dotnet-bootstrapper/xlf/LocalizableStrings.zh-Hans.xlf b/src/dotnet-bootstrapper/xlf/LocalizableStrings.zh-Hans.xlf index 1832bb6c..94caddf0 100644 --- a/src/dotnet-bootstrapper/xlf/LocalizableStrings.zh-Hans.xlf +++ b/src/dotnet-bootstrapper/xlf/LocalizableStrings.zh-Hans.xlf @@ -1,6 +1,12 @@  - + + + Bootstrapper help text + Bootstrapper help text + + + \ No newline at end of file diff --git a/src/dotnet-bootstrapper/xlf/LocalizableStrings.zh-Hant.xlf b/src/dotnet-bootstrapper/xlf/LocalizableStrings.zh-Hant.xlf index 4a6c48aa..28886312 100644 --- a/src/dotnet-bootstrapper/xlf/LocalizableStrings.zh-Hant.xlf +++ b/src/dotnet-bootstrapper/xlf/LocalizableStrings.zh-Hant.xlf @@ -1,6 +1,12 @@  - + + + Bootstrapper help text + Bootstrapper help text + + + \ No newline at end of file diff --git a/src/dotnet-core-uninstall/Shared/Configs/CommandLineConfigs.cs b/src/dotnet-core-uninstall/Shared/Configs/CommandLineConfigs.cs index 863b07c8..290e6df1 100644 --- a/src/dotnet-core-uninstall/Shared/Configs/CommandLineConfigs.cs +++ b/src/dotnet-core-uninstall/Shared/Configs/CommandLineConfigs.cs @@ -28,6 +28,7 @@ internal static class CommandLineConfigs private static readonly string DryRunCommandName = "dry-run"; private static readonly string WhatIfCommandName = "whatif"; private static readonly string RemoveCommandName = "remove"; + private static readonly string UninstallCommandName = "uninstall"; public static readonly RootCommand UninstallRootCommand = new RootCommand( RuntimeInfo.RunningOnWindows ? LocalizableStrings.UninstallNoOptionDescriptionWindows @@ -220,6 +221,7 @@ static CommandLineConfigs() UninstallRootCommand.AddCommand(ListCommand); UninstallRootCommand.AddCommand(DryRunCommand); UninstallRootCommand.AddCommand(RemoveCommand); + RemoveCommand.Aliases.Add(UninstallCommandName); // The verbiage that makes the most sense from the bootstrapper would be 'uninstall', so just adding an alias permits more code sharing UninstallRootCommand.AddCommand(VersionSubcommand); if (RuntimeInfo.RunningOnOSX) diff --git a/src/dotnet-core-uninstall/dotnet-core-uninstall.csproj b/src/dotnet-core-uninstall/dotnet-core-uninstall.csproj index fd483523..5f61daa7 100644 --- a/src/dotnet-core-uninstall/dotnet-core-uninstall.csproj +++ b/src/dotnet-core-uninstall/dotnet-core-uninstall.csproj @@ -2,6 +2,7 @@ dotnet-core-uninstall Exe + true win-x86;osx-x64;osx-arm64 true net8.0 From 80beb9c829e9f3e805417bf06322ae741f1446e9 Mon Sep 17 00:00:00 2001 From: Forgind <12969783+Forgind@users.noreply.github.com> Date: Mon, 21 Apr 2025 18:25:20 -0700 Subject: [PATCH 05/20] Little push --- src/dotnet-bootstrapper/BootstrapperCommandParser.cs | 2 +- .../Shared/Configs/CommandLineConfigs.cs | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/dotnet-bootstrapper/BootstrapperCommandParser.cs b/src/dotnet-bootstrapper/BootstrapperCommandParser.cs index ef6b8137..f4ff9a15 100644 --- a/src/dotnet-bootstrapper/BootstrapperCommandParser.cs +++ b/src/dotnet-bootstrapper/BootstrapperCommandParser.cs @@ -24,7 +24,7 @@ static BootstrapperCommandParser() BootstrapperRootCommand.AddCommand(CommandLineConfigs.ListCommand); BootstrapperRootCommand.AddCommand(CommandLineConfigs.RemoveCommand); BootstrapperRootCommand.AddCommand(HelpCommand); - VersionCommand.Handler = CommandHandler.Create(() => + HelpCommand.Handler = CommandHandler.Create(() => { Console.WriteLine(LocalizableStrings.BootstrapperHelp); }); diff --git a/src/dotnet-core-uninstall/Shared/Configs/CommandLineConfigs.cs b/src/dotnet-core-uninstall/Shared/Configs/CommandLineConfigs.cs index 290e6df1..23935d08 100644 --- a/src/dotnet-core-uninstall/Shared/Configs/CommandLineConfigs.cs +++ b/src/dotnet-core-uninstall/Shared/Configs/CommandLineConfigs.cs @@ -20,7 +20,7 @@ namespace Microsoft.DotNet.Tools.Uninstall.Shared.Configs { - internal static class CommandLineConfigs + public static class CommandLineConfigs { public static Parser UninstallCommandParser; @@ -185,7 +185,7 @@ internal static class CommandLineConfigs ForceOption }; - public static readonly Dictionary VerbosityLevels = new Dictionary + internal static readonly Dictionary VerbosityLevels = new Dictionary { { "q", VerbosityLevel.Quiet }, { "quiet", VerbosityLevel.Quiet }, { "m", VerbosityLevel.Minimal }, { "minimal", VerbosityLevel.Minimal }, @@ -221,7 +221,7 @@ static CommandLineConfigs() UninstallRootCommand.AddCommand(ListCommand); UninstallRootCommand.AddCommand(DryRunCommand); UninstallRootCommand.AddCommand(RemoveCommand); - RemoveCommand.Aliases.Add(UninstallCommandName); // The verbiage that makes the most sense from the bootstrapper would be 'uninstall', so just adding an alias permits more code sharing + RemoveCommand.AddAlias(UninstallCommandName); // The verbiage that makes the most sense from the bootstrapper would be 'uninstall', so just adding an alias permits more code sharing UninstallRootCommand.AddCommand(VersionSubcommand); if (RuntimeInfo.RunningOnOSX) @@ -314,7 +314,7 @@ public static Option GetUninstallMainOption(this CommandResult commandResult) return specifiedOption; } - public static BundleType GetTypeSelection(this ParseResult parseResult) + internal static BundleType GetTypeSelection(this ParseResult parseResult) { var supportedBundleTypes = SupportedBundleTypeConfigs.GetSupportedBundleTypes(); @@ -328,7 +328,7 @@ public static BundleType GetTypeSelection(this ParseResult parseResult) typeSelection; } - public static BundleArch GetArchSelection(this ParseResult parseResult) + internal static BundleArch GetArchSelection(this ParseResult parseResult) { var archSelection = new[] { @@ -345,7 +345,7 @@ public static BundleArch GetArchSelection(this ParseResult parseResult) archSelection; } - public static VerbosityLevel GetVerbosityLevel(this CommandResult commandResult) + internal static VerbosityLevel GetVerbosityLevel(this CommandResult commandResult) { var optionResult = commandResult.FindResultFor(VerbosityOption); From 59ee6587fa6e795a87557cb333a5ad730bd35483 Mon Sep 17 00:00:00 2001 From: Eduardo Villalpando Mello Date: Wed, 23 Apr 2025 13:35:37 -0700 Subject: [PATCH 06/20] Add Packages --- .../BootstrapperCommandParser.cs | 2 +- src/dotnet-bootstrapper/DotNetReleasesHandler.cs | 16 ++++++++++++++++ .../dotnet-bootstrapper.csproj | 2 ++ 3 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 src/dotnet-bootstrapper/DotNetReleasesHandler.cs diff --git a/src/dotnet-bootstrapper/BootstrapperCommandParser.cs b/src/dotnet-bootstrapper/BootstrapperCommandParser.cs index f4ff9a15..78d64e88 100644 --- a/src/dotnet-bootstrapper/BootstrapperCommandParser.cs +++ b/src/dotnet-bootstrapper/BootstrapperCommandParser.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. +// 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. using System; diff --git a/src/dotnet-bootstrapper/DotNetReleasesHandler.cs b/src/dotnet-bootstrapper/DotNetReleasesHandler.cs new file mode 100644 index 00000000..1ebbcf7a --- /dev/null +++ b/src/dotnet-bootstrapper/DotNetReleasesHandler.cs @@ -0,0 +1,16 @@ +using System; +using Microsoft.Deployment.DotNet.Releases; + +namespace Microsoft.DotNet.Tools.Bootstrapper; + +internal static class DotNetReleasesHandler +{ + internal static Uri[] DotNetReleasesIndexFeeds = new Uri[] + { + new("https://builds.dotnet.microsoft.com/dotnet/release-metadata/releases-index.json"), + new("https://dotnetcli.blob.core.windows.net/dotnet/release-metadata/2.1/releases.json") + }; + + internal static Uri DotNetRelesesMetadataFeed(string channelVersion) => + new($"https://builds.dotnet.microsoft.com/dotnet/release-metadata/{channelVersion}/releases.json"); +} diff --git a/src/dotnet-bootstrapper/dotnet-bootstrapper.csproj b/src/dotnet-bootstrapper/dotnet-bootstrapper.csproj index 1f570479..a5f0cfdf 100644 --- a/src/dotnet-bootstrapper/dotnet-bootstrapper.csproj +++ b/src/dotnet-bootstrapper/dotnet-bootstrapper.csproj @@ -22,10 +22,12 @@ + + From c623aa75d97aeaa25605f64d9c82a92dff65a22c Mon Sep 17 00:00:00 2001 From: Eduardo Villalpando Mello Date: Wed, 23 Apr 2025 16:52:44 -0700 Subject: [PATCH 07/20] Add DotNetReleasesHandler --- .../DotNetReleasesHandler.cs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/dotnet-bootstrapper/DotNetReleasesHandler.cs b/src/dotnet-bootstrapper/DotNetReleasesHandler.cs index 1ebbcf7a..54359f73 100644 --- a/src/dotnet-bootstrapper/DotNetReleasesHandler.cs +++ b/src/dotnet-bootstrapper/DotNetReleasesHandler.cs @@ -1,16 +1,17 @@ -using System; +using System; +using System.Linq; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; using Microsoft.Deployment.DotNet.Releases; namespace Microsoft.DotNet.Tools.Bootstrapper; internal static class DotNetReleasesHandler { - internal static Uri[] DotNetReleasesIndexFeeds = new Uri[] + public static async Task GetChannelAsync(string channelVersion) { - new("https://builds.dotnet.microsoft.com/dotnet/release-metadata/releases-index.json"), - new("https://dotnetcli.blob.core.windows.net/dotnet/release-metadata/2.1/releases.json") - }; - - internal static Uri DotNetRelesesMetadataFeed(string channelVersion) => - new($"https://builds.dotnet.microsoft.com/dotnet/release-metadata/{channelVersion}/releases.json"); + ProductCollection productCollection = await ProductCollection.GetAsync(); + return productCollection.FirstOrDefault((Product product) => product.ProductVersion.Equals(channelVersion)); + } } From 6a89e20fe946c3aec0493bf1d27126fcc54151fd Mon Sep 17 00:00:00 2001 From: Eduardo Villalpando Mello Date: Fri, 25 Apr 2025 09:42:13 -0700 Subject: [PATCH 08/20] Add list command --- .../BootstrapperCommandParser.cs | 3 +- src/dotnet-bootstrapper/CommandBase.cs | 21 ++++++ .../Commands/List/ListCommand.cs | 62 ++++++++++++++++ .../Commands/List/ListCommandParser.cs | 36 ++++++++++ .../LocalizableStrings.Designer.cs | 72 +++++++++++++++++++ .../dotnet-bootstrapper.csproj | 3 +- 6 files changed, 195 insertions(+), 2 deletions(-) create mode 100644 src/dotnet-bootstrapper/CommandBase.cs create mode 100644 src/dotnet-bootstrapper/Commands/List/ListCommand.cs create mode 100644 src/dotnet-bootstrapper/Commands/List/ListCommandParser.cs create mode 100644 src/dotnet-bootstrapper/LocalizableStrings.Designer.cs diff --git a/src/dotnet-bootstrapper/BootstrapperCommandParser.cs b/src/dotnet-bootstrapper/BootstrapperCommandParser.cs index 78d64e88..ea85c55e 100644 --- a/src/dotnet-bootstrapper/BootstrapperCommandParser.cs +++ b/src/dotnet-bootstrapper/BootstrapperCommandParser.cs @@ -8,6 +8,7 @@ using System.CommandLine.Parsing; using System.Reflection; using Microsoft.DotNet.Tools.Uninstall.Shared.Configs; +using Microsoft.DotNet.Tools.Bootstrapper.Commands.List; namespace Microsoft.DotNet.Tools.Bootstrapper { @@ -21,7 +22,7 @@ internal static class BootstrapperCommandParser static BootstrapperCommandParser() { BootstrapperRootCommand.AddCommand(CommandLineConfigs.VersionSubcommand); - BootstrapperRootCommand.AddCommand(CommandLineConfigs.ListCommand); + BootstrapperRootCommand.AddCommand(ListCommandParser.GetCommand()); BootstrapperRootCommand.AddCommand(CommandLineConfigs.RemoveCommand); BootstrapperRootCommand.AddCommand(HelpCommand); HelpCommand.Handler = CommandHandler.Create(() => diff --git a/src/dotnet-bootstrapper/CommandBase.cs b/src/dotnet-bootstrapper/CommandBase.cs new file mode 100644 index 00000000..81ac86b5 --- /dev/null +++ b/src/dotnet-bootstrapper/CommandBase.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.CommandLine.Parsing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.DotNet.Tools.Bootstrapper +{ + public abstract class CommandBase + { + protected ParseResult _parseResult; + + protected CommandBase(ParseResult parseResult) + { + _parseResult = parseResult; + } + + public abstract int Execute(); + } +} diff --git a/src/dotnet-bootstrapper/Commands/List/ListCommand.cs b/src/dotnet-bootstrapper/Commands/List/ListCommand.cs new file mode 100644 index 00000000..2ad42e44 --- /dev/null +++ b/src/dotnet-bootstrapper/Commands/List/ListCommand.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections.Generic; +using System.CommandLine.Parsing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.Deployment.DotNet.Releases; +using Spectre.Console; + +namespace Microsoft.DotNet.Tools.Bootstrapper.Commands.List +{ + internal class ListCommand( + ParseResult parseResult) : CommandBase(parseResult) + { + private string _channel = parseResult.ValueForArgument(ListCommandParser.ChannelArgument); + private bool _allowPreviews = parseResult.ValueForOption(ListCommandParser.AllowPreviews); + public override int Execute() + { + List productCollection = [.. ProductCollection.GetAsync().Result]; + productCollection = [.. + productCollection.Where(product => !product.IsOutOfSupport() && (product.SupportPhase != SupportPhase.Preview || _allowPreviews))]; + + if (!string.IsNullOrEmpty(_channel)) + { + productCollection = [.. productCollection.Where(product => product.ProductVersion.Equals(_channel, StringComparison.OrdinalIgnoreCase))]; + } + + foreach (Product product in productCollection) + { + string productHeader = $"{product.ProductName} {product.ProductVersion}"; + Console.WriteLine(productHeader); + + Table productMetadataTable = new Table() + .Expand() + .AddColumn("Version") + .AddColumn("Release Date") + .AddColumn("SDK") + .AddColumn("Runtime") + .AddColumn("ASP.NET Runtime") + .AddColumn("Windows Desktop Runtime"); + + List releases = product.GetReleasesAsync().Result.ToList() + .Where(relase => !relase.IsPreview || _allowPreviews).ToList(); + + foreach (ProductRelease release in releases) + { + productMetadataTable.AddRow( + release.Version.ToString(), + release.ReleaseDate.ToString("yyyy-MM-dd"), + release.Sdks.FirstOrDefault()?.DisplayVersion ?? "N/A", + release.Runtime?.DisplayVersion ?? "N/A", + release.AspNetCoreRuntime?.DisplayVersion ?? "N/A", + release.WindowsDesktopRuntime?.DisplayVersion ?? "N/A"); + } + AnsiConsole.Write(productMetadataTable); + Console.WriteLine(); + } + + return 0; + } + } +} diff --git a/src/dotnet-bootstrapper/Commands/List/ListCommandParser.cs b/src/dotnet-bootstrapper/Commands/List/ListCommandParser.cs new file mode 100644 index 00000000..a7b90579 --- /dev/null +++ b/src/dotnet-bootstrapper/Commands/List/ListCommandParser.cs @@ -0,0 +1,36 @@ +using System.CommandLine; +using System.CommandLine.Invocation; +using System.CommandLine.Parsing; + +namespace Microsoft.DotNet.Tools.Bootstrapper.Commands.List +{ + internal class ListCommandParser + { + internal static Argument ChannelArgument = new Argument( + name: "channel", + description: "The channel to list sdks for. If not specified, all sdks will be listed.") + { + Arity = ArgumentArity.ZeroOrOne + }; + + internal static Option AllowPreviews = new Option( + "--allow-previews", + description: "Allow preview releases to be listed."); + + private static readonly Command Command = ConstructCommand(); + public static Command GetCommand() => Command; + private static Command ConstructCommand() + { + Command command = new("list", "List all sdks available for installation"); + command.AddArgument(ChannelArgument); + command.AddOption(AllowPreviews); + + command.Handler = CommandHandler.Create((ParseResult parseResult) => + { + return new ListCommand(parseResult).Execute(); + }); + + return command; + } + } +} diff --git a/src/dotnet-bootstrapper/LocalizableStrings.Designer.cs b/src/dotnet-bootstrapper/LocalizableStrings.Designer.cs new file mode 100644 index 00000000..834cf15f --- /dev/null +++ b/src/dotnet-bootstrapper/LocalizableStrings.Designer.cs @@ -0,0 +1,72 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Microsoft.DotNet.Tools.Bootstrapper { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class LocalizableStrings { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal LocalizableStrings() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.DotNet.Tools.Bootstrapper.LocalizableStrings", typeof(LocalizableStrings).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Bootstrapper help text. + /// + internal static string BootstrapperHelp { + get { + return ResourceManager.GetString("BootstrapperHelp", resourceCulture); + } + } + } +} diff --git a/src/dotnet-bootstrapper/dotnet-bootstrapper.csproj b/src/dotnet-bootstrapper/dotnet-bootstrapper.csproj index a5f0cfdf..5dfff852 100644 --- a/src/dotnet-bootstrapper/dotnet-bootstrapper.csproj +++ b/src/dotnet-bootstrapper/dotnet-bootstrapper.csproj @@ -26,7 +26,7 @@ - + @@ -42,6 +42,7 @@ ResXFileCodeGenerator LocalizableStrings.Designer.cs + Microsoft.DotNet.Tools.Bootstrapper From 12a155a5839932f0a812c1630c14c79666fb85da Mon Sep 17 00:00:00 2001 From: Eduardo Villalpando Mello Date: Fri, 25 Apr 2025 10:13:48 -0700 Subject: [PATCH 09/20] Add search and install commands --- .../BootstrapperCommandParser.cs | 6 +- .../Commands/Install/InstallCommand.cs | 56 +++++++++++++++++ .../Commands/Install/InstallCommandParser.cs | 35 +++++++++++ .../Commands/List/ListCommand.cs | 62 ------------------- .../Commands/List/ListCommandParser.cs | 36 ----------- .../Commands/Search/SearchCommand.cs | 60 ++++++++++++++++++ .../Commands/Search/SearchCommandParser.cs | 35 +++++++++++ .../DotNetReleasesHandler.cs | 17 ----- 8 files changed, 190 insertions(+), 117 deletions(-) create mode 100644 src/dotnet-bootstrapper/Commands/Install/InstallCommand.cs create mode 100644 src/dotnet-bootstrapper/Commands/Install/InstallCommandParser.cs delete mode 100644 src/dotnet-bootstrapper/Commands/List/ListCommand.cs delete mode 100644 src/dotnet-bootstrapper/Commands/List/ListCommandParser.cs create mode 100644 src/dotnet-bootstrapper/Commands/Search/SearchCommand.cs create mode 100644 src/dotnet-bootstrapper/Commands/Search/SearchCommandParser.cs delete mode 100644 src/dotnet-bootstrapper/DotNetReleasesHandler.cs diff --git a/src/dotnet-bootstrapper/BootstrapperCommandParser.cs b/src/dotnet-bootstrapper/BootstrapperCommandParser.cs index ea85c55e..85d4fa5f 100644 --- a/src/dotnet-bootstrapper/BootstrapperCommandParser.cs +++ b/src/dotnet-bootstrapper/BootstrapperCommandParser.cs @@ -8,7 +8,8 @@ using System.CommandLine.Parsing; using System.Reflection; using Microsoft.DotNet.Tools.Uninstall.Shared.Configs; -using Microsoft.DotNet.Tools.Bootstrapper.Commands.List; +using Microsoft.DotNet.Tools.Bootstrapper.Commands.Search; +using Microsoft.DotNet.Tools.Bootstrapper.Commands.Install; namespace Microsoft.DotNet.Tools.Bootstrapper { @@ -22,7 +23,8 @@ internal static class BootstrapperCommandParser static BootstrapperCommandParser() { BootstrapperRootCommand.AddCommand(CommandLineConfigs.VersionSubcommand); - BootstrapperRootCommand.AddCommand(ListCommandParser.GetCommand()); + BootstrapperRootCommand.AddCommand(SearchCommandParser.GetCommand()); + BootstrapperRootCommand.AddCommand(InstallCommandParser.GetCommand()); BootstrapperRootCommand.AddCommand(CommandLineConfigs.RemoveCommand); BootstrapperRootCommand.AddCommand(HelpCommand); HelpCommand.Handler = CommandHandler.Create(() => diff --git a/src/dotnet-bootstrapper/Commands/Install/InstallCommand.cs b/src/dotnet-bootstrapper/Commands/Install/InstallCommand.cs new file mode 100644 index 00000000..fd78b2f9 --- /dev/null +++ b/src/dotnet-bootstrapper/Commands/Install/InstallCommand.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using System.CommandLine.Parsing; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.Deployment.DotNet.Releases; + +namespace Microsoft.DotNet.Tools.Bootstrapper.Commands.Install; + +internal class InstallCommand( + ParseResult parseResult) : CommandBase(parseResult) +{ + private string _channel = parseResult.ValueForArgument(InstallCommandParser.ChannelArgument); + + public override int Execute() + { + string path = Environment.CurrentDirectory; + ProductCollection productCollection = ProductCollection.GetAsync().Result; + Product product = productCollection + .FirstOrDefault(p => string.IsNullOrEmpty(_channel) || p.ProductVersion.Equals(_channel, StringComparison.OrdinalIgnoreCase)); + + if (product == null) + { + Console.WriteLine($"No product found for channel: {_channel}"); + return 1; + } + + ProductRelease latestRelease = product.GetReleasesAsync().Result + .Where(release => !release.IsPreview) + .OrderByDescending(release => release.ReleaseDate) + .FirstOrDefault(); + + if (latestRelease == null) + { + Console.WriteLine($"No releases found for product: {product.ProductName}"); + return 1; + } + + Console.WriteLine($"Installing {product.ProductName} {latestRelease.Version}..."); + + foreach (ReleaseComponent component in latestRelease.Components) + { + Console.WriteLine($"Installing {component.Name} {component.DisplayVersion}..."); + + ReleaseFile releaseFile = component.Files.FirstOrDefault(file => file.Rid.Equals(Environment.OSVersion.Platform.ToString(), StringComparison.OrdinalIgnoreCase)); + + releaseFile.DownloadAsync(Path.Combine(path, ".net", component.Name)).Wait(); + + Console.WriteLine($"Downloaded {component.Name} {component.DisplayVersion} to {Path.Combine(path, component.Name)}"); + } + + return 0; + } +} diff --git a/src/dotnet-bootstrapper/Commands/Install/InstallCommandParser.cs b/src/dotnet-bootstrapper/Commands/Install/InstallCommandParser.cs new file mode 100644 index 00000000..d03b6e5c --- /dev/null +++ b/src/dotnet-bootstrapper/Commands/Install/InstallCommandParser.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.CommandLine; +using System.CommandLine.Invocation; +using System.CommandLine.Parsing; + +namespace Microsoft.DotNet.Tools.Bootstrapper.Commands.Install; + +internal class InstallCommandParser +{ + internal static Argument ChannelArgument = new Argument( + name: "channel", + description: "The channel to install sdks for. If not specified, It will take the latest.") + { + Arity = ArgumentArity.ZeroOrOne + }; + + private static readonly Command Command = ConstructCommand(); + + public static Command GetCommand() => Command; + + private static Command ConstructCommand() + { + Command command = new("install", "Install SDKs available for installation."); + command.AddArgument(ChannelArgument); + command.Handler = CommandHandler.Create((ParseResult parseResult) => + { + return new InstallCommand(parseResult).Execute(); + }); + return command; + } +} diff --git a/src/dotnet-bootstrapper/Commands/List/ListCommand.cs b/src/dotnet-bootstrapper/Commands/List/ListCommand.cs deleted file mode 100644 index 2ad42e44..00000000 --- a/src/dotnet-bootstrapper/Commands/List/ListCommand.cs +++ /dev/null @@ -1,62 +0,0 @@ -using System; -using System.Collections.Generic; -using System.CommandLine.Parsing; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Microsoft.Deployment.DotNet.Releases; -using Spectre.Console; - -namespace Microsoft.DotNet.Tools.Bootstrapper.Commands.List -{ - internal class ListCommand( - ParseResult parseResult) : CommandBase(parseResult) - { - private string _channel = parseResult.ValueForArgument(ListCommandParser.ChannelArgument); - private bool _allowPreviews = parseResult.ValueForOption(ListCommandParser.AllowPreviews); - public override int Execute() - { - List productCollection = [.. ProductCollection.GetAsync().Result]; - productCollection = [.. - productCollection.Where(product => !product.IsOutOfSupport() && (product.SupportPhase != SupportPhase.Preview || _allowPreviews))]; - - if (!string.IsNullOrEmpty(_channel)) - { - productCollection = [.. productCollection.Where(product => product.ProductVersion.Equals(_channel, StringComparison.OrdinalIgnoreCase))]; - } - - foreach (Product product in productCollection) - { - string productHeader = $"{product.ProductName} {product.ProductVersion}"; - Console.WriteLine(productHeader); - - Table productMetadataTable = new Table() - .Expand() - .AddColumn("Version") - .AddColumn("Release Date") - .AddColumn("SDK") - .AddColumn("Runtime") - .AddColumn("ASP.NET Runtime") - .AddColumn("Windows Desktop Runtime"); - - List releases = product.GetReleasesAsync().Result.ToList() - .Where(relase => !relase.IsPreview || _allowPreviews).ToList(); - - foreach (ProductRelease release in releases) - { - productMetadataTable.AddRow( - release.Version.ToString(), - release.ReleaseDate.ToString("yyyy-MM-dd"), - release.Sdks.FirstOrDefault()?.DisplayVersion ?? "N/A", - release.Runtime?.DisplayVersion ?? "N/A", - release.AspNetCoreRuntime?.DisplayVersion ?? "N/A", - release.WindowsDesktopRuntime?.DisplayVersion ?? "N/A"); - } - AnsiConsole.Write(productMetadataTable); - Console.WriteLine(); - } - - return 0; - } - } -} diff --git a/src/dotnet-bootstrapper/Commands/List/ListCommandParser.cs b/src/dotnet-bootstrapper/Commands/List/ListCommandParser.cs deleted file mode 100644 index a7b90579..00000000 --- a/src/dotnet-bootstrapper/Commands/List/ListCommandParser.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.CommandLine; -using System.CommandLine.Invocation; -using System.CommandLine.Parsing; - -namespace Microsoft.DotNet.Tools.Bootstrapper.Commands.List -{ - internal class ListCommandParser - { - internal static Argument ChannelArgument = new Argument( - name: "channel", - description: "The channel to list sdks for. If not specified, all sdks will be listed.") - { - Arity = ArgumentArity.ZeroOrOne - }; - - internal static Option AllowPreviews = new Option( - "--allow-previews", - description: "Allow preview releases to be listed."); - - private static readonly Command Command = ConstructCommand(); - public static Command GetCommand() => Command; - private static Command ConstructCommand() - { - Command command = new("list", "List all sdks available for installation"); - command.AddArgument(ChannelArgument); - command.AddOption(AllowPreviews); - - command.Handler = CommandHandler.Create((ParseResult parseResult) => - { - return new ListCommand(parseResult).Execute(); - }); - - return command; - } - } -} diff --git a/src/dotnet-bootstrapper/Commands/Search/SearchCommand.cs b/src/dotnet-bootstrapper/Commands/Search/SearchCommand.cs new file mode 100644 index 00000000..e31d0aa9 --- /dev/null +++ b/src/dotnet-bootstrapper/Commands/Search/SearchCommand.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections.Generic; +using System.CommandLine.Parsing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.Deployment.DotNet.Releases; +using Spectre.Console; + +namespace Microsoft.DotNet.Tools.Bootstrapper.Commands.Search; + +internal class SearchCommand( + ParseResult parseResult) : CommandBase(parseResult) +{ + private string _channel = parseResult.ValueForArgument(SearchCommandParser.ChannelArgument); + private bool _allowPreviews = parseResult.ValueForOption(SearchCommandParser.AllowPreviews); + public override int Execute() + { + List productCollection = [.. ProductCollection.GetAsync().Result]; + productCollection = [.. + productCollection.Where(product => !product.IsOutOfSupport() && (product.SupportPhase != SupportPhase.Preview || _allowPreviews))]; + + if (!string.IsNullOrEmpty(_channel)) + { + productCollection = [.. productCollection.Where(product => product.ProductVersion.Equals(_channel, StringComparison.OrdinalIgnoreCase))]; + } + + foreach (Product product in productCollection) + { + string productHeader = $"{product.ProductName} {product.ProductVersion}"; + Console.WriteLine(productHeader); + + Table productMetadataTable = new Table() + .AddColumn("Version") + .AddColumn("Release Date") + .AddColumn("SDK") + .AddColumn("Runtime") + .AddColumn("ASP.NET Runtime") + .AddColumn("Windows Desktop Runtime"); + + List releases = product.GetReleasesAsync().Result.ToList() + .Where(relase => !relase.IsPreview || _allowPreviews).ToList(); + + foreach (ProductRelease release in releases) + { + productMetadataTable.AddRow( + release.Version.ToString(), + release.ReleaseDate.ToString("yyyy-MM-dd"), + release.Sdks.FirstOrDefault()?.DisplayVersion ?? "N/A", + release.Runtime?.DisplayVersion ?? "N/A", + release.AspNetCoreRuntime?.DisplayVersion ?? "N/A", + release.WindowsDesktopRuntime?.DisplayVersion ?? "N/A"); + } + AnsiConsole.Write(productMetadataTable); + Console.WriteLine(); + } + + return 0; + } +} diff --git a/src/dotnet-bootstrapper/Commands/Search/SearchCommandParser.cs b/src/dotnet-bootstrapper/Commands/Search/SearchCommandParser.cs new file mode 100644 index 00000000..356e0d22 --- /dev/null +++ b/src/dotnet-bootstrapper/Commands/Search/SearchCommandParser.cs @@ -0,0 +1,35 @@ +using System.CommandLine; +using System.CommandLine.Invocation; +using System.CommandLine.Parsing; + +namespace Microsoft.DotNet.Tools.Bootstrapper.Commands.Search; + +internal class SearchCommandParser +{ + internal static Argument ChannelArgument = new Argument( + name: "channel", + description: "The channel to list sdks for. If not specified, all sdks will be listed.") + { + Arity = ArgumentArity.ZeroOrOne + }; + + internal static Option AllowPreviews = new Option( + "--allow-previews", + description: "Allow preview releases to be listed."); + + private static readonly Command Command = ConstructCommand(); + public static Command GetCommand() => Command; + private static Command ConstructCommand() + { + Command command = new("search", "Search for SDKs available for installation."); + command.AddArgument(ChannelArgument); + command.AddOption(AllowPreviews); + + command.Handler = CommandHandler.Create((ParseResult parseResult) => + { + return new SearchCommand(parseResult).Execute(); + }); + + return command; + } +} diff --git a/src/dotnet-bootstrapper/DotNetReleasesHandler.cs b/src/dotnet-bootstrapper/DotNetReleasesHandler.cs deleted file mode 100644 index 54359f73..00000000 --- a/src/dotnet-bootstrapper/DotNetReleasesHandler.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; -using System.Linq; -using System.Net.Http; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Deployment.DotNet.Releases; - -namespace Microsoft.DotNet.Tools.Bootstrapper; - -internal static class DotNetReleasesHandler -{ - public static async Task GetChannelAsync(string channelVersion) - { - ProductCollection productCollection = await ProductCollection.GetAsync(); - return productCollection.FirstOrDefault((Product product) => product.ProductVersion.Equals(channelVersion)); - } -} From cbdb765e1c81aa3415c5dd812513d674591df7a1 Mon Sep 17 00:00:00 2001 From: Eduardo Villalpando Mello Date: Mon, 28 Apr 2025 11:13:54 -0700 Subject: [PATCH 10/20] Add utilities to get install dir and version --- .../BootstrapperUtilities.cs | 53 +++++++++++++++++++ .../Commands/Install/InstallCommand.cs | 30 +++++++++-- .../Commands/Search/SearchCommand.cs | 2 +- 3 files changed, 79 insertions(+), 6 deletions(-) create mode 100644 src/dotnet-bootstrapper/BootstrapperUtilities.cs diff --git a/src/dotnet-bootstrapper/BootstrapperUtilities.cs b/src/dotnet-bootstrapper/BootstrapperUtilities.cs new file mode 100644 index 00000000..1173b2c9 --- /dev/null +++ b/src/dotnet-bootstrapper/BootstrapperUtilities.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.DotNet.Tools.Bootstrapper +{ + internal static class BootstrapperUtilities + { + public static string GetRID() + { + string operatingSystem = RuntimeInformation.OSDescription switch + { + string os when os.Contains("Windows") => "win", + string os when os.Contains("Linux") => "linux", + string os when os.Contains("Darwin") => "osx", + _ => null + }; + string architecture = RuntimeInformation.OSArchitecture switch + { + Architecture.X64 => "x64", + Architecture.X86 => "x86", + Architecture.Arm => "arm", + Architecture.Arm64 => "arm64", + _ => null + }; + + if (operatingSystem == null || architecture == null) + { + throw new PlatformNotSupportedException("Unsupported OS or architecture."); + } + + return $"{operatingSystem}-{architecture}"; + } + + public static string GetVersionToInstallInDirectory(string directoryPath) + { + // Add heuristic to determine the version to install based on project, environment, etc. + // For now, return a hardcoded version. + return "9.0"; + } + public static string GetInstallationDirectoryPath() + { + // Add heuristic to determine the installation directory based on project, environment, etc. + return Path.Join( + Environment.CurrentDirectory, + ".dotnet.test"); + } + } +} diff --git a/src/dotnet-bootstrapper/Commands/Install/InstallCommand.cs b/src/dotnet-bootstrapper/Commands/Install/InstallCommand.cs index fd78b2f9..0dca1df6 100644 --- a/src/dotnet-bootstrapper/Commands/Install/InstallCommand.cs +++ b/src/dotnet-bootstrapper/Commands/Install/InstallCommand.cs @@ -13,10 +13,17 @@ internal class InstallCommand( ParseResult parseResult) : CommandBase(parseResult) { private string _channel = parseResult.ValueForArgument(InstallCommandParser.ChannelArgument); + private string _rid = BootstrapperUtilities.GetRID(); public override int Execute() { - string path = Environment.CurrentDirectory; + // If no channel is specified, use the default channel. + if (string.IsNullOrEmpty(_channel)) + { + _channel = BootstrapperUtilities.GetVersionToInstallInDirectory( + Environment.CurrentDirectory); + } + ProductCollection productCollection = ProductCollection.GetAsync().Result; Product product = productCollection .FirstOrDefault(p => string.IsNullOrEmpty(_channel) || p.ProductVersion.Equals(_channel, StringComparison.OrdinalIgnoreCase)); @@ -44,11 +51,24 @@ public override int Execute() { Console.WriteLine($"Installing {component.Name} {component.DisplayVersion}..."); - ReleaseFile releaseFile = component.Files.FirstOrDefault(file => file.Rid.Equals(Environment.OSVersion.Platform.ToString(), StringComparison.OrdinalIgnoreCase)); - - releaseFile.DownloadAsync(Path.Combine(path, ".net", component.Name)).Wait(); + ReleaseFile releaseFile = component.Files.FirstOrDefault(file => file.Rid.Equals(_rid, StringComparison.OrdinalIgnoreCase)); + + string path = Path.Combine(BootstrapperUtilities.GetInstallationDirectoryPath(), releaseFile.FileName); + + releaseFile.DownloadAsync(path)?.Wait(); + + Console.WriteLine($"Downloaded {component.Name} {component.DisplayVersion} to {path}"); - Console.WriteLine($"Downloaded {component.Name} {component.DisplayVersion} to {Path.Combine(path, component.Name)}"); + // Execute the installer files + if (releaseFile.IsExecutable) + { + Console.WriteLine($"Executing {path}..."); + // System.Diagnostics.Process.Start(path)?.WaitForExit(); + } + else + { + Console.WriteLine($"File {path} is not executable."); + } } return 0; diff --git a/src/dotnet-bootstrapper/Commands/Search/SearchCommand.cs b/src/dotnet-bootstrapper/Commands/Search/SearchCommand.cs index e31d0aa9..b6fe955a 100644 --- a/src/dotnet-bootstrapper/Commands/Search/SearchCommand.cs +++ b/src/dotnet-bootstrapper/Commands/Search/SearchCommand.cs @@ -46,7 +46,7 @@ public override int Execute() productMetadataTable.AddRow( release.Version.ToString(), release.ReleaseDate.ToString("yyyy-MM-dd"), - release.Sdks.FirstOrDefault()?.DisplayVersion ?? "N/A", + release.Sdks.LastOrDefault().DisplayVersion ?? "N/A", release.Runtime?.DisplayVersion ?? "N/A", release.AspNetCoreRuntime?.DisplayVersion ?? "N/A", release.WindowsDesktopRuntime?.DisplayVersion ?? "N/A"); From f46bccc2ef0bde0d82b2df461c1478b0bcd76d1e Mon Sep 17 00:00:00 2001 From: Eduardo Villalpando Mello Date: Mon, 28 Apr 2025 11:17:44 -0700 Subject: [PATCH 11/20] Placeholder for actual installation --- .../Commands/Install/InstallCommand.cs | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/src/dotnet-bootstrapper/Commands/Install/InstallCommand.cs b/src/dotnet-bootstrapper/Commands/Install/InstallCommand.cs index 0dca1df6..33605758 100644 --- a/src/dotnet-bootstrapper/Commands/Install/InstallCommand.cs +++ b/src/dotnet-bootstrapper/Commands/Install/InstallCommand.cs @@ -59,16 +59,7 @@ public override int Execute() Console.WriteLine($"Downloaded {component.Name} {component.DisplayVersion} to {path}"); - // Execute the installer files - if (releaseFile.IsExecutable) - { - Console.WriteLine($"Executing {path}..."); - // System.Diagnostics.Process.Start(path)?.WaitForExit(); - } - else - { - Console.WriteLine($"File {path} is not executable."); - } + // Perform installation steps here } return 0; From 58580f2bc5fb841b5b774c535af74c07927bbbc6 Mon Sep 17 00:00:00 2001 From: Eduardo Villalpando Mello Date: Mon, 28 Apr 2025 11:55:50 -0700 Subject: [PATCH 12/20] Figure out version and installation path from global.json --- .../BootstrapperUtilities.cs | 37 ++++++++++++---- .../Commands/Install/InstallCommand.cs | 6 ++- .../Commands/Install/InstallCommandParser.cs | 8 ++++ .../GlobalJsonUtilities.cs | 42 +++++++++++++++++++ 4 files changed, 82 insertions(+), 11 deletions(-) create mode 100644 src/dotnet-bootstrapper/GlobalJsonUtilities.cs diff --git a/src/dotnet-bootstrapper/BootstrapperUtilities.cs b/src/dotnet-bootstrapper/BootstrapperUtilities.cs index 1173b2c9..6f5c41e5 100644 --- a/src/dotnet-bootstrapper/BootstrapperUtilities.cs +++ b/src/dotnet-bootstrapper/BootstrapperUtilities.cs @@ -5,6 +5,8 @@ using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; +using System.Text.Json; +using Microsoft.Deployment.DotNet.Releases; namespace Microsoft.DotNet.Tools.Bootstrapper { @@ -27,7 +29,7 @@ string os when os.Contains("Darwin") => "osx", Architecture.Arm64 => "arm64", _ => null }; - + if (operatingSystem == null || architecture == null) { throw new PlatformNotSupportedException("Unsupported OS or architecture."); @@ -36,18 +38,35 @@ string os when os.Contains("Darwin") => "osx", return $"{operatingSystem}-{architecture}"; } - public static string GetVersionToInstallInDirectory(string directoryPath) + public static string GetMajorVersionToInstallInDirectory(string basePath) { - // Add heuristic to determine the version to install based on project, environment, etc. - // For now, return a hardcoded version. - return "9.0"; + // Get the nearest global.json file. + JsonElement globalJson = GlobalJsonUtilities.GetNearestGlobalJson(basePath); + string sdkVersion = globalJson + .GetProperty("tools") + .GetProperty("dotnet") + .ToString(); + + ReleaseVersion version = ReleaseVersion.Parse(sdkVersion); + Console.WriteLine($"Found version {version.Major}.0 in global.json"); + return $"{version.Major}.0"; } + public static string GetInstallationDirectoryPath() { - // Add heuristic to determine the installation directory based on project, environment, etc. - return Path.Join( - Environment.CurrentDirectory, - ".dotnet.test"); + string globalJsonPath = GlobalJsonUtilities.GetNearestGlobalJsonPath(Environment.CurrentDirectory); + if (globalJsonPath == null) + { + throw new FileNotFoundException("No global.json file found in the directory tree."); + } + string directoryPath = Path.GetDirectoryName(globalJsonPath); + if (directoryPath == null) + { + throw new DirectoryNotFoundException("Directory path is null."); + } + + // TODO: Replace with the actual installation directory. + return Path.Combine(directoryPath, ".dotnet.test"); } } } diff --git a/src/dotnet-bootstrapper/Commands/Install/InstallCommand.cs b/src/dotnet-bootstrapper/Commands/Install/InstallCommand.cs index 33605758..2d218445 100644 --- a/src/dotnet-bootstrapper/Commands/Install/InstallCommand.cs +++ b/src/dotnet-bootstrapper/Commands/Install/InstallCommand.cs @@ -14,13 +14,15 @@ internal class InstallCommand( { private string _channel = parseResult.ValueForArgument(InstallCommandParser.ChannelArgument); private string _rid = BootstrapperUtilities.GetRID(); + private bool _allowPreviews = parseResult.ValueForOption(InstallCommandParser.AllowPreviews); + public override int Execute() { // If no channel is specified, use the default channel. if (string.IsNullOrEmpty(_channel)) { - _channel = BootstrapperUtilities.GetVersionToInstallInDirectory( + _channel = BootstrapperUtilities.GetMajorVersionToInstallInDirectory( Environment.CurrentDirectory); } @@ -35,7 +37,7 @@ public override int Execute() } ProductRelease latestRelease = product.GetReleasesAsync().Result - .Where(release => !release.IsPreview) + .Where(release => !release.IsPreview || _allowPreviews) .OrderByDescending(release => release.ReleaseDate) .FirstOrDefault(); diff --git a/src/dotnet-bootstrapper/Commands/Install/InstallCommandParser.cs b/src/dotnet-bootstrapper/Commands/Install/InstallCommandParser.cs index d03b6e5c..35853b8c 100644 --- a/src/dotnet-bootstrapper/Commands/Install/InstallCommandParser.cs +++ b/src/dotnet-bootstrapper/Commands/Install/InstallCommandParser.cs @@ -18,6 +18,10 @@ internal class InstallCommandParser Arity = ArgumentArity.ZeroOrOne }; + internal static Option AllowPreviews = new Option( + "--allow-previews", + description: "Allow preview releases to be installed."); + private static readonly Command Command = ConstructCommand(); public static Command GetCommand() => Command; @@ -25,7 +29,11 @@ internal class InstallCommandParser private static Command ConstructCommand() { Command command = new("install", "Install SDKs available for installation."); + command.AddArgument(ChannelArgument); + + command.AddOption(AllowPreviews); + command.Handler = CommandHandler.Create((ParseResult parseResult) => { return new InstallCommand(parseResult).Execute(); diff --git a/src/dotnet-bootstrapper/GlobalJsonUtilities.cs b/src/dotnet-bootstrapper/GlobalJsonUtilities.cs new file mode 100644 index 00000000..496ede98 --- /dev/null +++ b/src/dotnet-bootstrapper/GlobalJsonUtilities.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.IO; +using System.Text.Json; + +namespace Microsoft.DotNet.Tools.Bootstrapper +{ + internal static class GlobalJsonUtilities + { + public static string GetNearestGlobalJsonPath(string basePath) + { + // Example implementation: Traverse up the directory tree to find the nearest global.json file. + string currentPath = basePath; + while (!string.IsNullOrEmpty(currentPath)) + { + string globalJsonPath = Path.Combine(currentPath, "global.json"); + if (File.Exists(globalJsonPath)) + { + return globalJsonPath; + } + currentPath = Directory.GetParent(currentPath)?.FullName; + } + // If no global.json file is found, return null. + return null; + } + + public static JsonElement GetNearestGlobalJson(string basePath) + { + string globalJsonPath = GetNearestGlobalJsonPath(basePath); + + if (globalJsonPath == null) + { + throw new FileNotFoundException("No global.json file found in the directory tree."); + } + + return JsonDocument.Parse(File.ReadAllText(globalJsonPath)).RootElement; + } + } +} From 9838ae35cfefaa58ea94ac1f06ae092c8237070f Mon Sep 17 00:00:00 2001 From: Eduardo Villalpando Mello Date: Mon, 28 Apr 2025 13:32:25 -0700 Subject: [PATCH 13/20] Show latest sdk --- src/dotnet-bootstrapper/BootstrapperCommandParser.cs | 3 ++- .../Commands/Install/InstallCommand.cs | 2 +- .../Commands/Install/InstallCommandParser.cs | 8 ++++---- src/dotnet-bootstrapper/Commands/Search/SearchCommand.cs | 9 +++++++-- 4 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/dotnet-bootstrapper/BootstrapperCommandParser.cs b/src/dotnet-bootstrapper/BootstrapperCommandParser.cs index 85d4fa5f..57b4e51f 100644 --- a/src/dotnet-bootstrapper/BootstrapperCommandParser.cs +++ b/src/dotnet-bootstrapper/BootstrapperCommandParser.cs @@ -22,11 +22,12 @@ internal static class BootstrapperCommandParser static BootstrapperCommandParser() { - BootstrapperRootCommand.AddCommand(CommandLineConfigs.VersionSubcommand); BootstrapperRootCommand.AddCommand(SearchCommandParser.GetCommand()); BootstrapperRootCommand.AddCommand(InstallCommandParser.GetCommand()); BootstrapperRootCommand.AddCommand(CommandLineConfigs.RemoveCommand); + BootstrapperRootCommand.AddCommand(CommandLineConfigs.VersionSubcommand); BootstrapperRootCommand.AddCommand(HelpCommand); + HelpCommand.Handler = CommandHandler.Create(() => { Console.WriteLine(LocalizableStrings.BootstrapperHelp); diff --git a/src/dotnet-bootstrapper/Commands/Install/InstallCommand.cs b/src/dotnet-bootstrapper/Commands/Install/InstallCommand.cs index 2d218445..4d217869 100644 --- a/src/dotnet-bootstrapper/Commands/Install/InstallCommand.cs +++ b/src/dotnet-bootstrapper/Commands/Install/InstallCommand.cs @@ -12,7 +12,7 @@ namespace Microsoft.DotNet.Tools.Bootstrapper.Commands.Install; internal class InstallCommand( ParseResult parseResult) : CommandBase(parseResult) { - private string _channel = parseResult.ValueForArgument(InstallCommandParser.ChannelArgument); + private string _channel = parseResult.ValueForArgument(InstallCommandParser.VersionArgument); private string _rid = BootstrapperUtilities.GetRID(); private bool _allowPreviews = parseResult.ValueForOption(InstallCommandParser.AllowPreviews); diff --git a/src/dotnet-bootstrapper/Commands/Install/InstallCommandParser.cs b/src/dotnet-bootstrapper/Commands/Install/InstallCommandParser.cs index 35853b8c..b076accd 100644 --- a/src/dotnet-bootstrapper/Commands/Install/InstallCommandParser.cs +++ b/src/dotnet-bootstrapper/Commands/Install/InstallCommandParser.cs @@ -11,9 +11,9 @@ namespace Microsoft.DotNet.Tools.Bootstrapper.Commands.Install; internal class InstallCommandParser { - internal static Argument ChannelArgument = new Argument( - name: "channel", - description: "The channel to install sdks for. If not specified, It will take the latest.") + internal static Argument VersionArgument = new Argument( + name: "version", + description: "SDK version to install. If not specified, It will take the latest.") { Arity = ArgumentArity.ZeroOrOne }; @@ -30,7 +30,7 @@ private static Command ConstructCommand() { Command command = new("install", "Install SDKs available for installation."); - command.AddArgument(ChannelArgument); + command.AddArgument(VersionArgument); command.AddOption(AllowPreviews); diff --git a/src/dotnet-bootstrapper/Commands/Search/SearchCommand.cs b/src/dotnet-bootstrapper/Commands/Search/SearchCommand.cs index b6fe955a..1c32e891 100644 --- a/src/dotnet-bootstrapper/Commands/Search/SearchCommand.cs +++ b/src/dotnet-bootstrapper/Commands/Search/SearchCommand.cs @@ -33,7 +33,7 @@ public override int Execute() Table productMetadataTable = new Table() .AddColumn("Version") .AddColumn("Release Date") - .AddColumn("SDK") + .AddColumn("Latest SDK") .AddColumn("Runtime") .AddColumn("ASP.NET Runtime") .AddColumn("Windows Desktop Runtime"); @@ -43,10 +43,15 @@ public override int Execute() foreach (ProductRelease release in releases) { + // Get release.Sdks latest version + var latestSdk = release.Sdks + .OrderByDescending(sdk => sdk.Version) + .FirstOrDefault(); + productMetadataTable.AddRow( release.Version.ToString(), release.ReleaseDate.ToString("yyyy-MM-dd"), - release.Sdks.LastOrDefault().DisplayVersion ?? "N/A", + latestSdk?.DisplayVersion ?? "N/A", release.Runtime?.DisplayVersion ?? "N/A", release.AspNetCoreRuntime?.DisplayVersion ?? "N/A", release.WindowsDesktopRuntime?.DisplayVersion ?? "N/A"); From 3bbdddc1f78f5c722d1648c432dd210968315cce Mon Sep 17 00:00:00 2001 From: Eduardo Villalpando Mello Date: Mon, 28 Apr 2025 13:34:52 -0700 Subject: [PATCH 14/20] Add no warn for strongname --- src/dotnet-bootstrapper/dotnet-bootstrapper.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/src/dotnet-bootstrapper/dotnet-bootstrapper.csproj b/src/dotnet-bootstrapper/dotnet-bootstrapper.csproj index 5dfff852..1deea4ca 100644 --- a/src/dotnet-bootstrapper/dotnet-bootstrapper.csproj +++ b/src/dotnet-bootstrapper/dotnet-bootstrapper.csproj @@ -17,6 +17,7 @@ preview Microsoft.DotNet.Tools.Bootstrapper + $(NoWarn);CS8002 From cbc2b7d5534bef793bf7b7f49287a38be9a22124 Mon Sep 17 00:00:00 2001 From: Eduardo Villalpando Mello Date: Mon, 28 Apr 2025 14:35:27 -0700 Subject: [PATCH 15/20] Add common options --- .../BootstrapperUtilities.cs | 2 +- src/dotnet-bootstrapper/Commands/Common.cs | 16 +++++ .../Commands/Install/InstallCommand.cs | 62 +++++++++++++++---- .../Commands/Install/InstallCommandParser.cs | 8 +-- .../Commands/Search/SearchCommand.cs | 2 +- .../Commands/Search/SearchCommandParser.cs | 6 +- 6 files changed, 72 insertions(+), 24 deletions(-) create mode 100644 src/dotnet-bootstrapper/Commands/Common.cs diff --git a/src/dotnet-bootstrapper/BootstrapperUtilities.cs b/src/dotnet-bootstrapper/BootstrapperUtilities.cs index 6f5c41e5..8e5cd1c7 100644 --- a/src/dotnet-bootstrapper/BootstrapperUtilities.cs +++ b/src/dotnet-bootstrapper/BootstrapperUtilities.cs @@ -66,7 +66,7 @@ public static string GetInstallationDirectoryPath() } // TODO: Replace with the actual installation directory. - return Path.Combine(directoryPath, ".dotnet.test"); + return Path.Combine(directoryPath, ".dotnet.local"); } } } diff --git a/src/dotnet-bootstrapper/Commands/Common.cs b/src/dotnet-bootstrapper/Commands/Common.cs new file mode 100644 index 00000000..b3c1006e --- /dev/null +++ b/src/dotnet-bootstrapper/Commands/Common.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.CommandLine; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.DotNet.Tools.Bootstrapper.Commands +{ + internal static class Common + { + internal static Option AllowPreviewsOptions = new Option( + "--allow-previews", + description: "Include pre-release sdk versions"); + } +} diff --git a/src/dotnet-bootstrapper/Commands/Install/InstallCommand.cs b/src/dotnet-bootstrapper/Commands/Install/InstallCommand.cs index 4d217869..06ac9932 100644 --- a/src/dotnet-bootstrapper/Commands/Install/InstallCommand.cs +++ b/src/dotnet-bootstrapper/Commands/Install/InstallCommand.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.CommandLine.Parsing; using System.IO; +using System.IO.Compression; using System.Linq; using System.Text; using System.Threading.Tasks; @@ -12,27 +13,27 @@ namespace Microsoft.DotNet.Tools.Bootstrapper.Commands.Install; internal class InstallCommand( ParseResult parseResult) : CommandBase(parseResult) { - private string _channel = parseResult.ValueForArgument(InstallCommandParser.VersionArgument); + private string _version = parseResult.ValueForArgument(InstallCommandParser.VersionArgument); private string _rid = BootstrapperUtilities.GetRID(); - private bool _allowPreviews = parseResult.ValueForOption(InstallCommandParser.AllowPreviews); + private bool _allowPreviews = parseResult.ValueForOption(InstallCommandParser.AllowPreviewsOption); public override int Execute() { // If no channel is specified, use the default channel. - if (string.IsNullOrEmpty(_channel)) + if (string.IsNullOrEmpty(_version)) { - _channel = BootstrapperUtilities.GetMajorVersionToInstallInDirectory( + _version = BootstrapperUtilities.GetMajorVersionToInstallInDirectory( Environment.CurrentDirectory); } ProductCollection productCollection = ProductCollection.GetAsync().Result; Product product = productCollection - .FirstOrDefault(p => string.IsNullOrEmpty(_channel) || p.ProductVersion.Equals(_channel, StringComparison.OrdinalIgnoreCase)); + .FirstOrDefault(p => string.IsNullOrEmpty(_version) || p.ProductVersion.Equals(_version, StringComparison.OrdinalIgnoreCase)); if (product == null) { - Console.WriteLine($"No product found for channel: {_channel}"); + Console.WriteLine($"No product found for channel: {_version}"); return 1; } @@ -49,21 +50,56 @@ public override int Execute() Console.WriteLine($"Installing {product.ProductName} {latestRelease.Version}..."); + string installationDirectoryPath = BootstrapperUtilities.GetInstallationDirectoryPath(); + foreach (ReleaseComponent component in latestRelease.Components) { - Console.WriteLine($"Installing {component.Name} {component.DisplayVersion}..."); + Console.WriteLine($"Installing {component.Name}..."); + DownloadAndExtractReleaseComponentFiles(component, installationDirectoryPath); + } + + return 0; + } - ReleaseFile releaseFile = component.Files.FirstOrDefault(file => file.Rid.Equals(_rid, StringComparison.OrdinalIgnoreCase)); + private static void DownloadAndExtractReleaseComponentFiles(ReleaseComponent component, string basePath) + { + if (component is WindowsDesktopReleaseComponent && !OperatingSystem.IsWindows()) + { + return; + } - string path = Path.Combine(BootstrapperUtilities.GetInstallationDirectoryPath(), releaseFile.FileName); + ReleaseFile releaseFile = component.Files.FirstOrDefault(file => + file.Rid.Equals(BootstrapperUtilities.GetRID(), StringComparison.OrdinalIgnoreCase) && (file.Name.EndsWith(".zip") || file.Name.EndsWith(".tar.gz"))); - releaseFile.DownloadAsync(path)?.Wait(); + if (string.IsNullOrEmpty(releaseFile?.FileName)) + { + Console.WriteLine($"\tNo suitable file found for {component.Name}"); + return; + } - Console.WriteLine($"Downloaded {component.Name} {component.DisplayVersion} to {path}"); + string zipPath = Path.Combine(basePath, releaseFile.FileName); - // Perform installation steps here + if (File.Exists(zipPath)) + { + Console.WriteLine($"\t{component.Name} already exists at {zipPath}"); + return; } - return 0; + try + { + releaseFile.DownloadAsync(zipPath)?.Wait(); + + // Extract the downloaded file + ZipFile.ExtractToDirectory(zipPath, Path.ChangeExtension(zipPath, "")); + + Console.WriteLine($"\tExtracted {component.Name} to {Path.ChangeExtension(zipPath, "")}"); + + // Delete the downloaded file + File.Delete(zipPath); + } + catch (IOException) + { + return; + } } } diff --git a/src/dotnet-bootstrapper/Commands/Install/InstallCommandParser.cs b/src/dotnet-bootstrapper/Commands/Install/InstallCommandParser.cs index b076accd..a0de571d 100644 --- a/src/dotnet-bootstrapper/Commands/Install/InstallCommandParser.cs +++ b/src/dotnet-bootstrapper/Commands/Install/InstallCommandParser.cs @@ -15,12 +15,10 @@ internal class InstallCommandParser name: "version", description: "SDK version to install. If not specified, It will take the latest.") { - Arity = ArgumentArity.ZeroOrOne + Arity = ArgumentArity.ZeroOrOne, }; - internal static Option AllowPreviews = new Option( - "--allow-previews", - description: "Allow preview releases to be installed."); + internal static Option AllowPreviewsOption = Common.AllowPreviewsOptions; private static readonly Command Command = ConstructCommand(); @@ -32,7 +30,7 @@ private static Command ConstructCommand() command.AddArgument(VersionArgument); - command.AddOption(AllowPreviews); + command.AddOption(AllowPreviewsOption); command.Handler = CommandHandler.Create((ParseResult parseResult) => { diff --git a/src/dotnet-bootstrapper/Commands/Search/SearchCommand.cs b/src/dotnet-bootstrapper/Commands/Search/SearchCommand.cs index 1c32e891..1bd42c6f 100644 --- a/src/dotnet-bootstrapper/Commands/Search/SearchCommand.cs +++ b/src/dotnet-bootstrapper/Commands/Search/SearchCommand.cs @@ -13,7 +13,7 @@ internal class SearchCommand( ParseResult parseResult) : CommandBase(parseResult) { private string _channel = parseResult.ValueForArgument(SearchCommandParser.ChannelArgument); - private bool _allowPreviews = parseResult.ValueForOption(SearchCommandParser.AllowPreviews); + private bool _allowPreviews = parseResult.ValueForOption(SearchCommandParser.AllowPreviewsOption); public override int Execute() { List productCollection = [.. ProductCollection.GetAsync().Result]; diff --git a/src/dotnet-bootstrapper/Commands/Search/SearchCommandParser.cs b/src/dotnet-bootstrapper/Commands/Search/SearchCommandParser.cs index 356e0d22..7f947e08 100644 --- a/src/dotnet-bootstrapper/Commands/Search/SearchCommandParser.cs +++ b/src/dotnet-bootstrapper/Commands/Search/SearchCommandParser.cs @@ -13,9 +13,7 @@ internal class SearchCommandParser Arity = ArgumentArity.ZeroOrOne }; - internal static Option AllowPreviews = new Option( - "--allow-previews", - description: "Allow preview releases to be listed."); + internal static Option AllowPreviewsOption = Common.AllowPreviewsOptions; private static readonly Command Command = ConstructCommand(); public static Command GetCommand() => Command; @@ -23,7 +21,7 @@ private static Command ConstructCommand() { Command command = new("search", "Search for SDKs available for installation."); command.AddArgument(ChannelArgument); - command.AddOption(AllowPreviews); + command.AddOption(AllowPreviewsOption); command.Handler = CommandHandler.Create((ParseResult parseResult) => { From 4df438ddfb4e00d5e5f755ea61580b0257dda402 Mon Sep 17 00:00:00 2001 From: Eduardo Villalpando Mello Date: Tue, 29 Apr 2025 09:51:59 -0700 Subject: [PATCH 16/20] search: localize date --- src/dotnet-bootstrapper/Commands/Search/SearchCommand.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/dotnet-bootstrapper/Commands/Search/SearchCommand.cs b/src/dotnet-bootstrapper/Commands/Search/SearchCommand.cs index 1bd42c6f..579b117f 100644 --- a/src/dotnet-bootstrapper/Commands/Search/SearchCommand.cs +++ b/src/dotnet-bootstrapper/Commands/Search/SearchCommand.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.CommandLine.Parsing; +using System.Globalization; using System.Linq; using System.Text; using System.Threading.Tasks; @@ -50,7 +51,7 @@ public override int Execute() productMetadataTable.AddRow( release.Version.ToString(), - release.ReleaseDate.ToString("yyyy-MM-dd"), + release.ReleaseDate.ToString("d", CultureInfo.CurrentUICulture), latestSdk?.DisplayVersion ?? "N/A", release.Runtime?.DisplayVersion ?? "N/A", release.AspNetCoreRuntime?.DisplayVersion ?? "N/A", From 7b35bd6e852b9697dfc0d20373355069cac688f4 Mon Sep 17 00:00:00 2001 From: Eduardo Villalpando Mello Date: Wed, 30 Apr 2025 13:20:27 -0700 Subject: [PATCH 17/20] Get RuntimeIdentifier from RuntimeInformation.RuntimeIdentifier --- .../BootstrapperUtilities.cs | 26 ------------------- .../Commands/Install/InstallCommand.cs | 4 +-- 2 files changed, 2 insertions(+), 28 deletions(-) diff --git a/src/dotnet-bootstrapper/BootstrapperUtilities.cs b/src/dotnet-bootstrapper/BootstrapperUtilities.cs index 8e5cd1c7..504c5232 100644 --- a/src/dotnet-bootstrapper/BootstrapperUtilities.cs +++ b/src/dotnet-bootstrapper/BootstrapperUtilities.cs @@ -12,32 +12,6 @@ namespace Microsoft.DotNet.Tools.Bootstrapper { internal static class BootstrapperUtilities { - public static string GetRID() - { - string operatingSystem = RuntimeInformation.OSDescription switch - { - string os when os.Contains("Windows") => "win", - string os when os.Contains("Linux") => "linux", - string os when os.Contains("Darwin") => "osx", - _ => null - }; - string architecture = RuntimeInformation.OSArchitecture switch - { - Architecture.X64 => "x64", - Architecture.X86 => "x86", - Architecture.Arm => "arm", - Architecture.Arm64 => "arm64", - _ => null - }; - - if (operatingSystem == null || architecture == null) - { - throw new PlatformNotSupportedException("Unsupported OS or architecture."); - } - - return $"{operatingSystem}-{architecture}"; - } - public static string GetMajorVersionToInstallInDirectory(string basePath) { // Get the nearest global.json file. diff --git a/src/dotnet-bootstrapper/Commands/Install/InstallCommand.cs b/src/dotnet-bootstrapper/Commands/Install/InstallCommand.cs index 06ac9932..a21ebd18 100644 --- a/src/dotnet-bootstrapper/Commands/Install/InstallCommand.cs +++ b/src/dotnet-bootstrapper/Commands/Install/InstallCommand.cs @@ -4,6 +4,7 @@ using System.IO; using System.IO.Compression; using System.Linq; +using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; using Microsoft.Deployment.DotNet.Releases; @@ -14,7 +15,6 @@ internal class InstallCommand( ParseResult parseResult) : CommandBase(parseResult) { private string _version = parseResult.ValueForArgument(InstallCommandParser.VersionArgument); - private string _rid = BootstrapperUtilities.GetRID(); private bool _allowPreviews = parseResult.ValueForOption(InstallCommandParser.AllowPreviewsOption); @@ -69,7 +69,7 @@ private static void DownloadAndExtractReleaseComponentFiles(ReleaseComponent com } ReleaseFile releaseFile = component.Files.FirstOrDefault(file => - file.Rid.Equals(BootstrapperUtilities.GetRID(), StringComparison.OrdinalIgnoreCase) && (file.Name.EndsWith(".zip") || file.Name.EndsWith(".tar.gz"))); + file.Rid.Equals(RuntimeInformation.RuntimeIdentifier, StringComparison.OrdinalIgnoreCase) && (file.Name.EndsWith(".zip") || file.Name.EndsWith(".tar.gz"))); if (string.IsNullOrEmpty(releaseFile?.FileName)) { From a9a47f574c9add73f6aad096a1a5168f9c09f811 Mon Sep 17 00:00:00 2001 From: Eduardo Villalpando Mello Date: Wed, 30 Apr 2025 13:40:03 -0700 Subject: [PATCH 18/20] Add docs to utils classes and address some pr comments --- .../BootstrapperUtilities.cs | 62 ++++++++++++++----- .../GlobalJsonUtilities.cs | 19 ++++++ 2 files changed, 65 insertions(+), 16 deletions(-) diff --git a/src/dotnet-bootstrapper/BootstrapperUtilities.cs b/src/dotnet-bootstrapper/BootstrapperUtilities.cs index 504c5232..1ea4df38 100644 --- a/src/dotnet-bootstrapper/BootstrapperUtilities.cs +++ b/src/dotnet-bootstrapper/BootstrapperUtilities.cs @@ -10,36 +10,66 @@ namespace Microsoft.DotNet.Tools.Bootstrapper { + /// + /// Provides utility methods for handling .NET SDK installation and configuration. + /// internal static class BootstrapperUtilities { + /// + /// Retrieves the major and minor version of the .NET SDK specified in the nearest global.json file + /// within the given base path. + /// + /// The base directory path to search for the global.json file. + /// + /// A string representing the major and minor version (e.g., "6.0") of the .NET SDK specified + /// in the global.json file. + /// + /// + /// Thrown when the required keys ("tools" or "dotnet") are not found in the global.json file. + /// public static string GetMajorVersionToInstallInDirectory(string basePath) { - // Get the nearest global.json file. - JsonElement globalJson = GlobalJsonUtilities.GetNearestGlobalJson(basePath); - string sdkVersion = globalJson - .GetProperty("tools") - .GetProperty("dotnet") - .ToString(); + try + { + // Get the nearest global.json file. + JsonElement globalJson = GlobalJsonUtilities.GetNearestGlobalJson(basePath); + string sdkVersion = globalJson + .GetProperty("tools") + .GetProperty("dotnet") + .ToString(); - ReleaseVersion version = ReleaseVersion.Parse(sdkVersion); - Console.WriteLine($"Found version {version.Major}.0 in global.json"); - return $"{version.Major}.0"; + ReleaseVersion version = ReleaseVersion.Parse(sdkVersion); + Console.WriteLine($"Found version {version.Major}.{version.Minor} in global.json"); + return $"{version.Major}.{version.Minor}"; + } + catch (KeyNotFoundException e) + { + throw new KeyNotFoundException("The specified key was not found in the global.json", e); + } } + /// + /// Retrieves the installation directory path for the .NET SDK based on the nearest global.json file. + /// + /// + /// The full path to the installation directory, combining the directory of the global.json file + /// and a predefined subdirectory (".dotnet.local"). + /// + /// + /// Thrown when no global.json file is found in the directory tree. + /// + /// + /// Thrown when the directory path derived from the global.json file is null. + /// public static string GetInstallationDirectoryPath() { string globalJsonPath = GlobalJsonUtilities.GetNearestGlobalJsonPath(Environment.CurrentDirectory); if (globalJsonPath == null) { - throw new FileNotFoundException("No global.json file found in the directory tree."); + throw new FileNotFoundException($"No global.json file found in the directory tree.", Environment.CurrentDirectory); } string directoryPath = Path.GetDirectoryName(globalJsonPath); - if (directoryPath == null) - { - throw new DirectoryNotFoundException("Directory path is null."); - } - - // TODO: Replace with the actual installation directory. + // TODO: Replace with the actual installation directory. return Path.Combine(directoryPath, ".dotnet.local"); } } diff --git a/src/dotnet-bootstrapper/GlobalJsonUtilities.cs b/src/dotnet-bootstrapper/GlobalJsonUtilities.cs index 496ede98..76a69aaf 100644 --- a/src/dotnet-bootstrapper/GlobalJsonUtilities.cs +++ b/src/dotnet-bootstrapper/GlobalJsonUtilities.cs @@ -8,8 +8,18 @@ namespace Microsoft.DotNet.Tools.Bootstrapper { + /// + /// Provides utility methods for working with global.json files in a directory tree. + /// internal static class GlobalJsonUtilities { + /// + /// Finds the nearest global.json file path by traversing up the directory tree starting from the specified base path. + /// + /// The starting directory path to search for the global.json file. + /// + /// The full path to the nearest global.json file if found; otherwise, null. + /// public static string GetNearestGlobalJsonPath(string basePath) { // Example implementation: Traverse up the directory tree to find the nearest global.json file. @@ -27,6 +37,15 @@ public static string GetNearestGlobalJsonPath(string basePath) return null; } + /// + /// Retrieves the contents of the nearest global.json file as a JsonElement by traversing up the directory tree. + /// + /// The starting directory path to search for the global.json file. + /// + /// A JsonElement representing the contents of the nearest global.json file. + /// + /// Thrown if no global.json file is found in the directory tree. + /// Thrown if global.json cannot be parsed as json public static JsonElement GetNearestGlobalJson(string basePath) { string globalJsonPath = GetNearestGlobalJsonPath(basePath); From 1697e1ea99f84e4b08489a422828bec906241afd Mon Sep 17 00:00:00 2001 From: Eduardo Villalpando Mello Date: Thu, 8 May 2025 14:31:28 -0700 Subject: [PATCH 19/20] Reduce scope --- .../BootstrapperCommandParser.cs | 6 - .../BootstrapperUtilities.cs | 76 ------------- .../{ => Commands}/CommandBase.cs | 2 +- .../Commands/Install/InstallCommand.cs | 105 ------------------ .../Commands/Install/InstallCommandParser.cs | 41 ------- .../GlobalJsonUtilities.cs | 33 ++++++ 6 files changed, 34 insertions(+), 229 deletions(-) delete mode 100644 src/dotnet-bootstrapper/BootstrapperUtilities.cs rename src/dotnet-bootstrapper/{ => Commands}/CommandBase.cs (88%) delete mode 100644 src/dotnet-bootstrapper/Commands/Install/InstallCommand.cs delete mode 100644 src/dotnet-bootstrapper/Commands/Install/InstallCommandParser.cs diff --git a/src/dotnet-bootstrapper/BootstrapperCommandParser.cs b/src/dotnet-bootstrapper/BootstrapperCommandParser.cs index 57b4e51f..7921da76 100644 --- a/src/dotnet-bootstrapper/BootstrapperCommandParser.cs +++ b/src/dotnet-bootstrapper/BootstrapperCommandParser.cs @@ -6,10 +6,7 @@ using System.CommandLine.Builder; using System.CommandLine.Invocation; using System.CommandLine.Parsing; -using System.Reflection; -using Microsoft.DotNet.Tools.Uninstall.Shared.Configs; using Microsoft.DotNet.Tools.Bootstrapper.Commands.Search; -using Microsoft.DotNet.Tools.Bootstrapper.Commands.Install; namespace Microsoft.DotNet.Tools.Bootstrapper { @@ -23,9 +20,6 @@ internal static class BootstrapperCommandParser static BootstrapperCommandParser() { BootstrapperRootCommand.AddCommand(SearchCommandParser.GetCommand()); - BootstrapperRootCommand.AddCommand(InstallCommandParser.GetCommand()); - BootstrapperRootCommand.AddCommand(CommandLineConfigs.RemoveCommand); - BootstrapperRootCommand.AddCommand(CommandLineConfigs.VersionSubcommand); BootstrapperRootCommand.AddCommand(HelpCommand); HelpCommand.Handler = CommandHandler.Create(() => diff --git a/src/dotnet-bootstrapper/BootstrapperUtilities.cs b/src/dotnet-bootstrapper/BootstrapperUtilities.cs deleted file mode 100644 index 1ea4df38..00000000 --- a/src/dotnet-bootstrapper/BootstrapperUtilities.cs +++ /dev/null @@ -1,76 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Runtime.InteropServices; -using System.Text; -using System.Threading.Tasks; -using System.Text.Json; -using Microsoft.Deployment.DotNet.Releases; - -namespace Microsoft.DotNet.Tools.Bootstrapper -{ - /// - /// Provides utility methods for handling .NET SDK installation and configuration. - /// - internal static class BootstrapperUtilities - { - /// - /// Retrieves the major and minor version of the .NET SDK specified in the nearest global.json file - /// within the given base path. - /// - /// The base directory path to search for the global.json file. - /// - /// A string representing the major and minor version (e.g., "6.0") of the .NET SDK specified - /// in the global.json file. - /// - /// - /// Thrown when the required keys ("tools" or "dotnet") are not found in the global.json file. - /// - public static string GetMajorVersionToInstallInDirectory(string basePath) - { - try - { - // Get the nearest global.json file. - JsonElement globalJson = GlobalJsonUtilities.GetNearestGlobalJson(basePath); - string sdkVersion = globalJson - .GetProperty("tools") - .GetProperty("dotnet") - .ToString(); - - ReleaseVersion version = ReleaseVersion.Parse(sdkVersion); - Console.WriteLine($"Found version {version.Major}.{version.Minor} in global.json"); - return $"{version.Major}.{version.Minor}"; - } - catch (KeyNotFoundException e) - { - throw new KeyNotFoundException("The specified key was not found in the global.json", e); - } - } - - /// - /// Retrieves the installation directory path for the .NET SDK based on the nearest global.json file. - /// - /// - /// The full path to the installation directory, combining the directory of the global.json file - /// and a predefined subdirectory (".dotnet.local"). - /// - /// - /// Thrown when no global.json file is found in the directory tree. - /// - /// - /// Thrown when the directory path derived from the global.json file is null. - /// - public static string GetInstallationDirectoryPath() - { - string globalJsonPath = GlobalJsonUtilities.GetNearestGlobalJsonPath(Environment.CurrentDirectory); - if (globalJsonPath == null) - { - throw new FileNotFoundException($"No global.json file found in the directory tree.", Environment.CurrentDirectory); - } - string directoryPath = Path.GetDirectoryName(globalJsonPath); - // TODO: Replace with the actual installation directory. - return Path.Combine(directoryPath, ".dotnet.local"); - } - } -} diff --git a/src/dotnet-bootstrapper/CommandBase.cs b/src/dotnet-bootstrapper/Commands/CommandBase.cs similarity index 88% rename from src/dotnet-bootstrapper/CommandBase.cs rename to src/dotnet-bootstrapper/Commands/CommandBase.cs index 81ac86b5..a14cf73f 100644 --- a/src/dotnet-bootstrapper/CommandBase.cs +++ b/src/dotnet-bootstrapper/Commands/CommandBase.cs @@ -5,7 +5,7 @@ using System.Text; using System.Threading.Tasks; -namespace Microsoft.DotNet.Tools.Bootstrapper +namespace Microsoft.DotNet.Tools.Bootstrapper.Commands { public abstract class CommandBase { diff --git a/src/dotnet-bootstrapper/Commands/Install/InstallCommand.cs b/src/dotnet-bootstrapper/Commands/Install/InstallCommand.cs deleted file mode 100644 index a21ebd18..00000000 --- a/src/dotnet-bootstrapper/Commands/Install/InstallCommand.cs +++ /dev/null @@ -1,105 +0,0 @@ -using System; -using System.Collections.Generic; -using System.CommandLine.Parsing; -using System.IO; -using System.IO.Compression; -using System.Linq; -using System.Runtime.InteropServices; -using System.Text; -using System.Threading.Tasks; -using Microsoft.Deployment.DotNet.Releases; - -namespace Microsoft.DotNet.Tools.Bootstrapper.Commands.Install; - -internal class InstallCommand( - ParseResult parseResult) : CommandBase(parseResult) -{ - private string _version = parseResult.ValueForArgument(InstallCommandParser.VersionArgument); - private bool _allowPreviews = parseResult.ValueForOption(InstallCommandParser.AllowPreviewsOption); - - - public override int Execute() - { - // If no channel is specified, use the default channel. - if (string.IsNullOrEmpty(_version)) - { - _version = BootstrapperUtilities.GetMajorVersionToInstallInDirectory( - Environment.CurrentDirectory); - } - - ProductCollection productCollection = ProductCollection.GetAsync().Result; - Product product = productCollection - .FirstOrDefault(p => string.IsNullOrEmpty(_version) || p.ProductVersion.Equals(_version, StringComparison.OrdinalIgnoreCase)); - - if (product == null) - { - Console.WriteLine($"No product found for channel: {_version}"); - return 1; - } - - ProductRelease latestRelease = product.GetReleasesAsync().Result - .Where(release => !release.IsPreview || _allowPreviews) - .OrderByDescending(release => release.ReleaseDate) - .FirstOrDefault(); - - if (latestRelease == null) - { - Console.WriteLine($"No releases found for product: {product.ProductName}"); - return 1; - } - - Console.WriteLine($"Installing {product.ProductName} {latestRelease.Version}..."); - - string installationDirectoryPath = BootstrapperUtilities.GetInstallationDirectoryPath(); - - foreach (ReleaseComponent component in latestRelease.Components) - { - Console.WriteLine($"Installing {component.Name}..."); - DownloadAndExtractReleaseComponentFiles(component, installationDirectoryPath); - } - - return 0; - } - - private static void DownloadAndExtractReleaseComponentFiles(ReleaseComponent component, string basePath) - { - if (component is WindowsDesktopReleaseComponent && !OperatingSystem.IsWindows()) - { - return; - } - - ReleaseFile releaseFile = component.Files.FirstOrDefault(file => - file.Rid.Equals(RuntimeInformation.RuntimeIdentifier, StringComparison.OrdinalIgnoreCase) && (file.Name.EndsWith(".zip") || file.Name.EndsWith(".tar.gz"))); - - if (string.IsNullOrEmpty(releaseFile?.FileName)) - { - Console.WriteLine($"\tNo suitable file found for {component.Name}"); - return; - } - - string zipPath = Path.Combine(basePath, releaseFile.FileName); - - if (File.Exists(zipPath)) - { - Console.WriteLine($"\t{component.Name} already exists at {zipPath}"); - return; - } - - try - { - releaseFile.DownloadAsync(zipPath)?.Wait(); - - // Extract the downloaded file - ZipFile.ExtractToDirectory(zipPath, Path.ChangeExtension(zipPath, "")); - - Console.WriteLine($"\tExtracted {component.Name} to {Path.ChangeExtension(zipPath, "")}"); - - // Delete the downloaded file - File.Delete(zipPath); - } - catch (IOException) - { - return; - } - } -} diff --git a/src/dotnet-bootstrapper/Commands/Install/InstallCommandParser.cs b/src/dotnet-bootstrapper/Commands/Install/InstallCommandParser.cs deleted file mode 100644 index a0de571d..00000000 --- a/src/dotnet-bootstrapper/Commands/Install/InstallCommandParser.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using System.CommandLine; -using System.CommandLine.Invocation; -using System.CommandLine.Parsing; - -namespace Microsoft.DotNet.Tools.Bootstrapper.Commands.Install; - -internal class InstallCommandParser -{ - internal static Argument VersionArgument = new Argument( - name: "version", - description: "SDK version to install. If not specified, It will take the latest.") - { - Arity = ArgumentArity.ZeroOrOne, - }; - - internal static Option AllowPreviewsOption = Common.AllowPreviewsOptions; - - private static readonly Command Command = ConstructCommand(); - - public static Command GetCommand() => Command; - - private static Command ConstructCommand() - { - Command command = new("install", "Install SDKs available for installation."); - - command.AddArgument(VersionArgument); - - command.AddOption(AllowPreviewsOption); - - command.Handler = CommandHandler.Create((ParseResult parseResult) => - { - return new InstallCommand(parseResult).Execute(); - }); - return command; - } -} diff --git a/src/dotnet-bootstrapper/GlobalJsonUtilities.cs b/src/dotnet-bootstrapper/GlobalJsonUtilities.cs index 76a69aaf..ad808b85 100644 --- a/src/dotnet-bootstrapper/GlobalJsonUtilities.cs +++ b/src/dotnet-bootstrapper/GlobalJsonUtilities.cs @@ -5,6 +5,7 @@ using System.Threading.Tasks; using System.IO; using System.Text.Json; +using Microsoft.Deployment.DotNet.Releases; namespace Microsoft.DotNet.Tools.Bootstrapper { @@ -57,5 +58,37 @@ public static JsonElement GetNearestGlobalJson(string basePath) return JsonDocument.Parse(File.ReadAllText(globalJsonPath)).RootElement; } + /// + /// Retrieves the major and minor version of the .NET SDK specified in the nearest global.json file + /// within the given base path. + /// + /// The base directory path to search for the global.json file. + /// + /// A string representing the major and minor version (e.g., "6.0") of the .NET SDK specified + /// in the global.json file. + /// + /// + /// Thrown when the required keys ("tools" or "dotnet") are not found in the global.json file. + /// + public static string GetMajorVersionToInstallInDirectory(string basePath) + { + try + { + // Get the nearest global.json file. + JsonElement globalJson = GlobalJsonUtilities.GetNearestGlobalJson(basePath); + string sdkVersion = globalJson + .GetProperty("tools") + .GetProperty("dotnet") + .ToString(); + + ReleaseVersion version = ReleaseVersion.Parse(sdkVersion); + Console.WriteLine($"Found version {version.Major}.{version.Minor} in global.json"); + return $"{version.Major}.{version.Minor}"; + } + catch (KeyNotFoundException e) + { + throw new KeyNotFoundException("The specified key was not found in the global.json", e); + } + } } } From 822f5e28f0c1ad2b75be7f0d1971051875fbc352 Mon Sep 17 00:00:00 2001 From: Eduardo Villalpando Mello Date: Thu, 8 May 2025 14:46:18 -0700 Subject: [PATCH 20/20] Remove project dependency --- src/dotnet-bootstrapper/dotnet-bootstrapper.csproj | 1 - 1 file changed, 1 deletion(-) diff --git a/src/dotnet-bootstrapper/dotnet-bootstrapper.csproj b/src/dotnet-bootstrapper/dotnet-bootstrapper.csproj index 1deea4ca..acd992ab 100644 --- a/src/dotnet-bootstrapper/dotnet-bootstrapper.csproj +++ b/src/dotnet-bootstrapper/dotnet-bootstrapper.csproj @@ -27,7 +27,6 @@ -