Skip to content

Add uninstall/list/help support-ish #389

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 10 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
200 changes: 184 additions & 16 deletions src/dotnet-bootstrapper/BootstrapperCommandParser.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
// 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;
using System.Collections;
using System.Collections.Generic;
using System.CommandLine;
using System.CommandLine.Builder;
using System.CommandLine.Help;
using System.CommandLine.Invocation;
using System.CommandLine.Parsing;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text.Json;

namespace Microsoft.DotNet.Tools.Bootstrapper
{
Expand All @@ -16,34 +23,195 @@

public static RootCommand BootstrapperRootCommand = new RootCommand("dotnet bootstrapper");

public static readonly Command VersionCommand = new Command("--version");
public static readonly Command VersionCommand = new("--version")
{
Handler = CommandHandler.Create(() =>
{
Assembly assembly = Assembly.GetEntryAssembly() ?? Assembly.GetExecutingAssembly();
Console.WriteLine(assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion ?? assembly.GetName().Version.ToString());
})
};

public static readonly Command HelpCommand = new("--help")
{
Handler = CommandHandler.Create(() =>
{
Console.WriteLine(LocalizableStrings.BootstrapperHelp);
})
};

public static readonly Command ListCommand = new("list", LocalizableStrings.ListCommandDescription)
{
Handler = CommandHandler.Create((ParseResult parseResult) =>
{
string dotnetDir = FindLocalDotnet(parseResult.ValueForOption(DotnetPath));
if (dotnetDir is null)
{
Console.WriteLine("dotnet executable not found. Ensure you execute this command from a directory with it.");
return;
}

foreach (string s in FindLocalSDKs(dotnetDir))
{
Console.WriteLine(s);
}
})
};

private static readonly Lazy<string> _assemblyVersion =
new Lazy<string>(() =>
public static readonly Command UninstallCommand = new("uninstall", LocalizableStrings.UninstallCommandDescription)
{
Handler = CommandHandler.Create((ParseResult parseResult) =>
{
var assembly = Assembly.GetEntryAssembly() ?? Assembly.GetExecutingAssembly();
var assemblyVersionAttribute = assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>();
if (assemblyVersionAttribute == null)
string dotnetDir = FindLocalDotnet(parseResult.ValueForOption(DotnetPath));
if (dotnetDir is null)
{
Console.WriteLine("dotnet executable not found. Ensure you execute this command from a directory with it.");
return;
}

string sdkToUninstall = parseResult.ValueForArgument(UninstallArgument);

IEnumerable<string> localSdks = FindLocalSDKs(dotnetDir).Where(sdk => sdk.EndsWith("foo"));
if (!localSdks.Any())
{
Console.WriteLine("Failed to find SDK " + "foo");
return;
}

string sdkFolder = Path.Combine(Path.GetDirectoryName(dotnetDir), "sdk");
foreach (string sdk in localSdks)
{
return assembly.GetName().Version.ToString();
Console.WriteLine("Deleting SDK " + sdk);
Directory.Delete(sdk, recursive: true);
}
else
})
};

public static readonly Command InstallCommand = new("install", LocalizableStrings.InstallCommandDescription)
{
Handler = CommandHandler.Create((ParseResult parseResult) =>
{
string dotnetDir = FindLocalDotnet(parseResult.ValueForOption(DotnetPath));
if (dotnetDir is null)
{
return assemblyVersionAttribute.InformationalVersion;
// install dotnet.exe
}
});
})
};

public static readonly Argument<string> UninstallArgument = new()
{
Name = "uninstall",
Arity = ArgumentArity.ExactlyOne
};

public static readonly Option<string> DotnetPath = new("--dotnetPath", getDefaultValue: () => null);

internal enum hostfxr_resolve_sdk2_result_key_t
{
resolved_sdk_dir = 0,
global_json_path = 1,
};

[DllImport("hostfxr", CharSet = CharSet.Auto, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
internal static extern int hostfxr_get_available_sdks(string exe_dir, hostfxr_get_available_sdks_result_fn result);

[UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Auto)]
internal delegate void hostfxr_get_available_sdks_result_fn(
hostfxr_resolve_sdk2_result_key_t key,
[MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)]
string[] value);

private static string[] FindLocalSDKs(string dotnetDir)
{
string[] resolvedPaths = null;
int returnCode = hostfxr_get_available_sdks(exe_dir: dotnetDir, result: (key, value) => resolvedPaths = value);
if (returnCode == 0)
{
return resolvedPaths ?? [];
}
else
{
throw new InvalidOperationException("Failed to find SDKs");
}
}

private static string FindLocalDotnet(string pathFromSwitch)
{
if (pathFromSwitch is not null)
{
string pathToDotnet = CheckFile(pathFromSwitch) ?? pathFromSwitch;
if (!File.Exists(pathToDotnet))
{
throw new ArgumentException($"Path {pathFromSwitch} does not lead to the dotnet executable.");
}

return pathToDotnet;
}

string currentDirectory = Directory.GetCurrentDirectory();

string path = FindAndParseGlobalJson(currentDirectory);
if (path is not null)
{
return path;
}

return CheckFile(currentDirectory) ??
CheckFile(Path.GetDirectoryName(currentDirectory)) ??
Directory.GetDirectories(currentDirectory).SelectMany(subdirectory =>
{
if (CheckFile(subdirectory) is string subDotnet)
{
return [subDotnet];
}

return Directory.GetDirectories(subdirectory).Select(CheckFile);
}).FirstOrDefault(path => path is not null);
}

private static string FindAndParseGlobalJson(string startingDirectory)
{
string currentDirectory = startingDirectory;
string globalJsonPath = Path.Combine(currentDirectory, "global.json");
while (!File.Exists(globalJsonPath))
{
startingDirectory = Path.GetDirectoryName(startingDirectory);
if (startingDirectory is null)
{
return null;
}

globalJsonPath = Path.Combine(currentDirectory, "global.json");
}

JsonDocument jsonDocument = JsonDocument.Parse(globalJsonPath);
jsonDocument.RootElement.

Check failure on line 189 in src/dotnet-bootstrapper/BootstrapperCommandParser.cs

View check run for this annotation

Azure Pipelines / dotnet.cli-lab (Build Mac OS Release_x64)

src/dotnet-bootstrapper/BootstrapperCommandParser.cs#L189

src/dotnet-bootstrapper/BootstrapperCommandParser.cs(189,38): error CS1001: (NETCORE_ENGINEERING_TELEMETRY=Build) Identifier expected

Check failure on line 189 in src/dotnet-bootstrapper/BootstrapperCommandParser.cs

View check run for this annotation

Azure Pipelines / dotnet.cli-lab (Build Mac OS Release_x64)

src/dotnet-bootstrapper/BootstrapperCommandParser.cs#L189

src/dotnet-bootstrapper/BootstrapperCommandParser.cs(189,38): error CS1002: (NETCORE_ENGINEERING_TELEMETRY=Build) ; expected

Check failure on line 189 in src/dotnet-bootstrapper/BootstrapperCommandParser.cs

View check run for this annotation

Azure Pipelines / dotnet.cli-lab (Build Mac OS Debug_arm64)

src/dotnet-bootstrapper/BootstrapperCommandParser.cs#L189

src/dotnet-bootstrapper/BootstrapperCommandParser.cs(189,38): error CS1001: (NETCORE_ENGINEERING_TELEMETRY=Build) Identifier expected

Check failure on line 189 in src/dotnet-bootstrapper/BootstrapperCommandParser.cs

View check run for this annotation

Azure Pipelines / dotnet.cli-lab (Build Mac OS Debug_arm64)

src/dotnet-bootstrapper/BootstrapperCommandParser.cs#L189

src/dotnet-bootstrapper/BootstrapperCommandParser.cs(189,38): error CS1002: (NETCORE_ENGINEERING_TELEMETRY=Build) ; expected

Check failure on line 189 in src/dotnet-bootstrapper/BootstrapperCommandParser.cs

View check run for this annotation

Azure Pipelines / dotnet.cli-lab (Build Mac OS Release_arm64)

src/dotnet-bootstrapper/BootstrapperCommandParser.cs#L189

src/dotnet-bootstrapper/BootstrapperCommandParser.cs(189,38): error CS1001: (NETCORE_ENGINEERING_TELEMETRY=Build) Identifier expected

Check failure on line 189 in src/dotnet-bootstrapper/BootstrapperCommandParser.cs

View check run for this annotation

Azure Pipelines / dotnet.cli-lab (Build Mac OS Release_arm64)

src/dotnet-bootstrapper/BootstrapperCommandParser.cs#L189

src/dotnet-bootstrapper/BootstrapperCommandParser.cs(189,38): error CS1002: (NETCORE_ENGINEERING_TELEMETRY=Build) ; expected

Check failure on line 189 in src/dotnet-bootstrapper/BootstrapperCommandParser.cs

View check run for this annotation

Azure Pipelines / dotnet.cli-lab (Build Mac OS Debug_x64)

src/dotnet-bootstrapper/BootstrapperCommandParser.cs#L189

src/dotnet-bootstrapper/BootstrapperCommandParser.cs(189,38): error CS1001: (NETCORE_ENGINEERING_TELEMETRY=Build) Identifier expected

Check failure on line 189 in src/dotnet-bootstrapper/BootstrapperCommandParser.cs

View check run for this annotation

Azure Pipelines / dotnet.cli-lab (Build Mac OS Debug_x64)

src/dotnet-bootstrapper/BootstrapperCommandParser.cs#L189

src/dotnet-bootstrapper/BootstrapperCommandParser.cs(189,38): error CS1002: (NETCORE_ENGINEERING_TELEMETRY=Build) ; expected

Check failure on line 189 in src/dotnet-bootstrapper/BootstrapperCommandParser.cs

View check run for this annotation

Azure Pipelines / dotnet.cli-lab

src/dotnet-bootstrapper/BootstrapperCommandParser.cs#L189

src/dotnet-bootstrapper/BootstrapperCommandParser.cs(189,38): error CS1001: (NETCORE_ENGINEERING_TELEMETRY=Build) Identifier expected

Check failure on line 189 in src/dotnet-bootstrapper/BootstrapperCommandParser.cs

View check run for this annotation

Azure Pipelines / dotnet.cli-lab

src/dotnet-bootstrapper/BootstrapperCommandParser.cs#L189

src/dotnet-bootstrapper/BootstrapperCommandParser.cs(189,38): error CS1002: (NETCORE_ENGINEERING_TELEMETRY=Build) ; expected

Check failure on line 189 in src/dotnet-bootstrapper/BootstrapperCommandParser.cs

View check run for this annotation

Azure Pipelines / dotnet.cli-lab

src/dotnet-bootstrapper/BootstrapperCommandParser.cs#L189

src/dotnet-bootstrapper/BootstrapperCommandParser.cs(189,38): error CS1001: (NETCORE_ENGINEERING_TELEMETRY=Build) Identifier expected

Check failure on line 189 in src/dotnet-bootstrapper/BootstrapperCommandParser.cs

View check run for this annotation

Azure Pipelines / dotnet.cli-lab

src/dotnet-bootstrapper/BootstrapperCommandParser.cs#L189

src/dotnet-bootstrapper/BootstrapperCommandParser.cs(189,38): error CS1002: (NETCORE_ENGINEERING_TELEMETRY=Build) ; expected
}

private static string CheckFile(string directory)
{
string fileToCheck = Path.Combine(directory, "dotnet.exe");
return File.Exists(fileToCheck) ? fileToCheck : null;
}

static BootstrapperCommandParser()
{
BootstrapperRootCommand.AddCommand(VersionCommand);
VersionCommand.Handler = CommandHandler.Create(() =>
{
Console.WriteLine(_assemblyVersion.Value);
});
BootstrapperRootCommand.AddCommand(HelpCommand);
BootstrapperRootCommand.AddCommand(ListCommand);
BootstrapperRootCommand.AddCommand(UninstallCommand);
BootstrapperRootCommand.AddCommand(InstallCommand);

ListCommand.AddOption(DotnetPath);
UninstallCommand.AddOption(DotnetPath);
InstallCommand.AddOption(DotnetPath);

UninstallCommand.AddArgument(UninstallArgument);

BootstrapParser = new CommandLineBuilder(BootstrapperRootCommand)
.UseDefaults()
// .UseHelpBuilder(context => new UninstallHelpBuilder(context.Console))
.UseHelpBuilder(context => new HelpBuilder(context.Console))
.Build();
}
}
Expand Down
90 changes: 90 additions & 0 deletions src/dotnet-bootstrapper/LocalizableStrings.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading