Pruning, add container command stub

This commit is contained in:
David Bennett 2026-02-02 23:21:01 -08:00
parent c4f99ae4ae
commit 5676031f5d
17 changed files with 184 additions and 283 deletions

View File

@ -18,12 +18,15 @@ set(HEADERS
inc/invocation.h
commands/RootCommand.h
commands/TestCommand.h
commands/ContainerCommand.h
workflows/WorkflowBase.h
workflows/CommonFlow.h
workflows/TestFlow.h
services/ContainerService.h
)
set(SOURCES
util.cpp
argument.cpp
command.cpp
context.cpp
@ -31,7 +34,9 @@ set(SOURCES
main.cpp
commands/RootCommand.cpp
commands/TestCommand.cpp
commands/ContainerCommand.cpp
workflows/WorkflowBase.cpp
workflows/CommonFlow.cpp
workflows/TestFlow.cpp
services/ContainerService.cpp
)

View File

@ -37,12 +37,10 @@ namespace wsl::windows::wslc
void Command::OutputIntroHeader() const
{
// TODO: Product name, version, copyright info in resources.
/*
std::wostringstream infoOut;
infoOut << L"Windows Subsystem for Linux Container CLI (Preview) v1.0.0" << std::endl;
infoOut << L"Copyright (c) Microsoft Corporation. All rights reserved." << std::endl;
PrintMessage(infoOut.str(), stdout);
*/
}
void Command::OutputHelp(const CommandException* exception) const
@ -249,15 +247,6 @@ namespace wsl::windows::wslc
}
}
// Finally, the link to the documentation pages
/* Omit for prototyping.
auto helpLink = HelpLink();
if (!helpLink.empty())
{
infoOut << std::endl << Localization::WSLCCLI_HelpLinkPreamble(helpLink) << std::endl;
}
*/
PrintMessage(infoOut.str(), stdout);
}
@ -622,117 +611,6 @@ namespace wsl::windows::wslc
ValidateArgumentsInternal(execArgs);
}
// Completion can produce one of several things if the completion context is appropriate:
// 1. Sub commands, if the context is immediately after this command.
// 2. Argument names, if a value is not expected.
// 3. Argument values, if one is expected.
void Command::Complete(CLIExecutionContext& context) const
{
/*
CompletionData& data = context.Get<Execution::Data::CompletionData>();
const std::string& word = data.Word();
// The word we are to complete is directly after the command, thus it's sub-commands are potentials.
if (data.BeforeWord().begin() == data.BeforeWord().end())
{
for (const auto& command : GetCommands())
{
if (word.empty() || Utility::CaseInsensitiveStartsWith(command->Name(), word))
{
context.Reporter.Completion() << command->Name() << std::endl;
}
// Allow for command aliases to be auto-completed
if (!(command->Aliases()).empty() && !word.empty())
{
for (const auto& commandAlias : command->Aliases())
{
if (Utility::CaseInsensitiveStartsWith(commandAlias, word))
{
context.Reporter.Completion() << commandAlias << std::endl;
}
}
}
}
}
// Consume what remains, if any, of the preceding values to determine what type the word is.
auto definedArgs = GetArguments();
Argument::GetCommon(definedArgs);
ParseArgumentsStateMachine stateMachine{ data.BeforeWord(), context.Args, std::move(definedArgs) };
// We don't care if there are errors along the way, just do the best that can be done and try to
// complete whatever would be next if the bad strings were simply ignored. To do that we just spin
// through the state until we reach our word.
while (stateMachine.Step());
const auto& state = stateMachine.GetState();
// This means that anything is possible, so argument names are on the table.
if (!state.Type() && !stateMachine.OnlyPositionalRemain())
{
// Use argument names if:
// 1. word is empty
// 2. word is just "-"
// 3. word starts with "--"
if (word.empty() ||
word == WSLC_CLI_ARGUMENT_IDENTIFIER_STRING ||
Utility::CaseInsensitiveStartsWith(word, WSLC_CLI_ARGUMENT_IDENTIFIER_STRING WSLC_CLI_ARGUMENT_IDENTIFIER_STRING))
{
for (const auto& arg : stateMachine.Arguments())
{
if (word.length() <= 2 || Utility::CaseInsensitiveStartsWith(arg.Name(), word.substr(2)))
{
context.Reporter.Completion() << WSLC_CLI_ARGUMENT_IDENTIFIER_CHAR << WSLC_CLI_ARGUMENT_IDENTIFIER_CHAR << arg.Name() << std::endl;
}
}
}
// Use argument aliases if the word is already one; allow cycling through them.
else if (Utility::CaseInsensitiveStartsWith(word, WSLC_CLI_ARGUMENT_IDENTIFIER_STRING) && word.length() == 2)
{
for (const auto& arg : stateMachine.Arguments())
{
if (arg.Alias() != ArgumentCommon::NoAlias)
{
context.Reporter.Completion() << WSLC_CLI_ARGUMENT_IDENTIFIER_CHAR << arg.Alias() << std::endl;
}
}
}
}
std::optional<Execution::Args::Type> typeToComplete = state.Type();
// We are not waiting on an argument value, so the next could be a positional if the incoming word is not an argument name.
// If there is one, offer to complete it.
if (!typeToComplete && (word.empty() || word[0] != WSLC_CLI_ARGUMENT_IDENTIFIER_CHAR))
{
const auto* nextPositional = stateMachine.NextPositional();
if (nextPositional)
{
typeToComplete = nextPositional->ExecArgType();
}
}
// To enable more complete scenarios, also attempt to parse any arguments after the word to complete.
// This will allow these later values to affect the result of the completion (for instance, if a specific source is listed).
{
ParseArgumentsStateMachine afterWordStateMachine{ data.AfterWord(), context.Args, stateMachine.Arguments() };
while (afterWordStateMachine.Step());
}
// Let the derived command take over supplying context sensitive argument value.
if (typeToComplete)
{
Complete(context, typeToComplete.value());
}
*/
}
void Command::Complete(CLIExecutionContext& context, Args::Type type) const
{
// Derived commands must supply context sensitive argument values.
}
void Command::Execute(CLIExecutionContext& context) const
{
if (context.Args.Contains(Args::Type::Help))

View File

@ -0,0 +1,62 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
#pragma once
#include "pch.h"
#include "ContainerCommand.h"
#include "WorkflowBase.h"
#include "TestFlow.h"
using namespace wsl::windows::common::wslutil;
using namespace wsl::windows::wslc::execution;
namespace wsl::windows::wslc
{
// Container Root Command
std::vector<std::unique_ptr<Command>> ContainerCommand::GetCommands() const
{
return InitializeFromMoveOnly<std::vector<std::unique_ptr<Command>>>({
std::make_unique<ContainerRunCommand>(FullName()),
});
}
std::vector<Argument> ContainerCommand::GetArguments() const
{
return {};
}
std::wstring_view ContainerCommand::ShortDescription() const
{
return { L"Container command" };
}
std::wstring_view ContainerCommand::LongDescription() const
{
return { L"Container command for demonstration purposes." };
}
void ContainerCommand::ExecuteInternal(CLIExecutionContext& context) const
{
PrintMessage(L"Container base command executing..", stdout);
}
// Container Run Command
std::vector<Argument> ContainerRunCommand::GetArguments() const
{
return {};
}
std::wstring_view ContainerRunCommand::ShortDescription() const
{
return { L"Run command" };
}
std::wstring_view ContainerRunCommand::LongDescription() const
{
return { L"Run command for demonstration purposes." };
}
void ContainerRunCommand::ExecuteInternal(CLIExecutionContext& context) const
{
PrintMessage(L"Container Run subcommand executing..", stdout);
}
}

View File

@ -0,0 +1,33 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
#pragma once
#include "command.h"
namespace wsl::windows::wslc
{
struct ContainerCommand final : public Command
{
constexpr static std::wstring_view CommandName = L"container";
ContainerCommand(std::wstring parent) : Command(CommandName, {}, parent, Visibility::Show) {}
std::vector<Argument> GetArguments() const override;
std::wstring_view ShortDescription() const override;
std::wstring_view LongDescription() const override;
std::vector<std::unique_ptr<Command>> GetCommands() const override;
protected:
void ExecuteInternal(CLIExecutionContext& context) const override;
};
struct ContainerRunCommand final : public Command
{
constexpr static std::wstring_view CommandName = L"run";
ContainerRunCommand(std::wstring parent) : Command(CommandName, {}, parent, Visibility::Show) {}
std::vector<Argument> GetArguments() const override;
std::wstring_view ShortDescription() const override;
std::wstring_view LongDescription() const override;
protected:
void ExecuteInternal(CLIExecutionContext& context) const override;
};
}

View File

@ -1,11 +1,14 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
#pragma once
#include "pch.h"
#include "util.h"
#include "RootCommand.h"
#include "WorkflowBase.h"
// Include all commands that parent to the root.
#include "TestCommand.h"
#include "ContainerCommand.h"
using namespace wsl::shared;
using namespace wsl::windows::common::wslutil;
@ -17,6 +20,8 @@ namespace wsl::windows::wslc
{
return InitializeFromMoveOnly<std::vector<std::unique_ptr<Command>>>({
std::make_unique<TestCommand>(FullName()),
std::make_unique<ContainerCommand>(FullName()),
std::make_unique<ContainerRunCommand>(FullName()),
});
}

View File

@ -4,6 +4,7 @@
#include "pch.h"
#include "TestCommand.h"
#include "WorkflowBase.h"
#include "CommonFlow.h"
#include "TestFlow.h"
using namespace wsl::windows::common::wslutil;

View File

@ -1,5 +1,6 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
#pragma once
#include "pch.h"
#include "argument.h"
#include "command.h"
@ -11,11 +12,6 @@ using namespace wsl::windows::wslc;
namespace wsl::windows::wslc::execution
{
void CLIExecutionContext::UpdateForArgs()
{
// Currently no-op.
}
void CLIExecutionContext::Terminate(HRESULT hr, std::string_view file, size_t line)
{
////Logging::Telemetry().LogCommandTermination(hr, file, line);
@ -30,19 +26,4 @@ namespace wsl::windows::wslc::execution
m_terminationHR = hr;
m_isTerminated = true;
}
void CLIExecutionContext::SetExecutionStage(ExecutionStage stage)
{
if (m_executionStage == stage)
{
return;
}
else if (m_executionStage > stage)
{
// Programmer error.
THROW_HR_MSG(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), "Reporting ExecutionStage to an earlier Stage.");
}
m_executionStage = stage;
}
}

View File

@ -65,7 +65,6 @@ namespace wsl::windows::wslc
}
command->ParseArguments(invocation, context.Args);
context.UpdateForArgs();
context.SetExecutingCommand(command.get());
command->ValidateArguments(context.Args);
}

View File

@ -100,9 +100,6 @@ namespace wsl::windows::wslc
virtual void ParseArguments(Invocation& inv, Args& execArgs) const;
virtual void ValidateArguments(Args& execArgs) const;
virtual void Complete(CLIExecutionContext& context) const;
virtual void Complete(CLIExecutionContext& context, Args::Type valueType) const;
virtual void Execute(CLIExecutionContext& context) const;
protected:

View File

@ -9,18 +9,6 @@
#include <string_view>
#define WSLC_CATCH_RESULT_EXCEPTION_STORE(exceptionHR) catch (const wil::ResultException& re) { exceptionHR = re.GetErrorCode(); }
#define WSLC_CATCH_HRESULT_EXCEPTION_STORE(exceptionHR) catch (const winrt::hresult_error& hre) { exceptionHR = hre.code(); }
#define WSLC_CATCH_COMMAND_EXCEPTION_STORE(exceptionHR) catch (const wsl::windows::wslc::CommandException&) { exceptionHR = WSLC_CLI_ERROR_INVALID_CL_ARGUMENTS; }
#define WSLC_CATCH_STD_EXCEPTION_STORE(exceptionHR, genericHR) catch (const std::exception&) { exceptionHR = genericHR; }
#define WSLC_CATCH_ALL_EXCEPTION_STORE(exceptionHR, genericHR) catch (...) { exceptionHR = genericHR; }
#define WSLC_CATCH_STORE(exceptionHR, genericHR) \
WSLC_CATCH_RESULT_EXCEPTION_STORE(exceptionHR) \
WSLC_CATCH_HRESULT_EXCEPTION_STORE(exceptionHR) \
WSLC_CATCH_COMMAND_EXCEPTION_STORE(exceptionHR) \
WSLC_CATCH_STD_EXCEPTION_STORE(exceptionHR, genericHR) \
WSLC_CATCH_ALL_EXCEPTION_STORE(exceptionHR, genericHR)
// Terminates the Context with some logging to indicate the location.
// Also returns from the current function.
#define WSLC_TERMINATE_CONTEXT_ARGS(_context_, _hr_, _ret_) \
@ -57,24 +45,6 @@ namespace wsl::windows::wslc::workflow
namespace wsl::windows::wslc::execution
{
enum class ExecutionStage : uint32_t
{
Initial = 0,
ParseArgs = 1000,
Discovery = 2000,
PreExecution = 3500,
Execution = 4000,
PostExecution = 5000,
};
// bit masks used as Context flags
enum class ContextFlag : int
{
None = 0x0,
};
DEFINE_ENUM_FLAG_OPERATORS(ContextFlag);
// The context within which all commands execute.
// Contains arguments via Args.
struct CLIExecutionContext : public wsl::windows::common::ExecutionContext
@ -91,27 +61,6 @@ namespace wsl::windows::wslc::execution
// The arguments given to execute.
Args Args;
// Applies changes based on the parsed args.
void UpdateForArgs();
// Gets context flags
ContextFlag GetFlags() const
{
return m_flags;
}
// Set context flags
void SetFlags(ContextFlag flags)
{
WI_SetAllFlags(m_flags, flags);
}
// Clear context flags
void ClearFlags(ContextFlag flags)
{
WI_ClearAllFlags(m_flags, flags);
}
// Returns a value indicating whether the context is terminated.
bool IsTerminated() const
{
@ -137,8 +86,6 @@ namespace wsl::windows::wslc::execution
// Set the termination hr of the context.
void SetTerminationHR(HRESULT hr);
virtual void SetExecutionStage(ExecutionStage stage);
// Gets the executing command
wsl::windows::wslc::Command* GetExecutingCommand() { return m_executingCommand; }
@ -148,8 +95,6 @@ namespace wsl::windows::wslc::execution
private:
bool m_isTerminated = false;
HRESULT m_terminationHR = S_OK;
ContextFlag m_flags = ContextFlag::None;
ExecutionStage m_executionStage = ExecutionStage::Initial;
wsl::windows::wslc::Command* m_executingCommand = nullptr;
};
}

View File

@ -1,26 +1,13 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
#pragma once
#include "pch.h"
#include <wil/token_helpers.h>
#include <winrt/Windows.System.Profile.h>
namespace wsl::windows::wslc::util
{
std::wstring GetOSVersion()
{
using namespace winrt::Windows::System::Profile;
auto versionInfo = AnalyticsInfo::VersionInfo();
std::wstring GetOSVersion();
uint64_t version = std::stoull(versionInfo.DeviceFamilyVersion().c_str());
uint16_t parts[4];
for (size_t i = 0; i < ARRAYSIZE(parts); ++i)
{
parts[i] = version & 0xFFFF;
version = version >> 16;
}
std::wostringstream strstr;
strstr << versionInfo.DeviceFamily().c_str() << L" v" << parts[3] << L'.' << parts[2] << L'.' << parts[1] << L'.' << parts[0];
return strstr.str();
}
bool IsRunningAsAdmin();
}

View File

@ -0,0 +1,34 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
#pragma once
#include "pch.h"
#include "util.h"
#include <wil/token_helpers.h>
#include <winrt/Windows.System.Profile.h>
namespace wsl::windows::wslc::util
{
std::wstring GetOSVersion()
{
using namespace winrt::Windows::System::Profile;
auto versionInfo = AnalyticsInfo::VersionInfo();
uint64_t version = std::stoull(versionInfo.DeviceFamilyVersion().c_str());
uint16_t parts[4];
for (size_t i = 0; i < ARRAYSIZE(parts); ++i)
{
parts[i] = version & 0xFFFF;
version = version >> 16;
}
std::wostringstream strstr;
strstr << versionInfo.DeviceFamily().c_str() << L" v" << parts[3] << L'.' << parts[2] << L'.' << parts[1] << L'.' << parts[0];
return strstr.str();
}
bool IsRunningAsAdmin()
{
return wil::test_token_membership(nullptr, SECURITY_NT_AUTHORITY, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS);
}
}

View File

@ -0,0 +1,22 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
#pragma once
#include "pch.h"
#include "util.h"
#include "context.h"
#include "WorkflowBase.h"
#include "CommonFlow.h"
using namespace wsl::windows::wslc::execution;
namespace wsl::windows::wslc::workflow
{
void EnsureRunningAsAdmin(CLIExecutionContext& context)
{
if (!util::IsRunningAsAdmin())
{
WSLC_TERMINATE_CONTEXT(WSLC_CLI_ERROR_COMMAND_REQUIRES_ADMIN);
}
}
}

View File

@ -0,0 +1,15 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
#pragma once
#include "context.h"
using wsl::windows::wslc::execution::CLIExecutionContext;
namespace wsl::windows::wslc::workflow
{
// Ensures that the process is running as admin.
// Required Args: None
// Inputs: None
// Outputs: None
void EnsureRunningAsAdmin(CLIExecutionContext& context);
}

View File

@ -3,7 +3,7 @@
#pragma once
#include "context.h"
using namespace wsl::windows::wslc::execution;
using wsl::windows::wslc::execution::CLIExecutionContext;
namespace wsl::windows::wslc::workflow
{

View File

@ -1,27 +1,15 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
#pragma once
#include "pch.h"
#include "WorkflowBase.h"
#include <wil/token_helpers.h>
#include <wil/result_macros.h>
using namespace wsl::shared;
using namespace wsl::windows::common;
using namespace wsl::windows::wslc;
using namespace wsl::windows::wslc::execution;
using namespace std::string_literals;
using namespace winrt::Windows::Foundation;
namespace wsl::windows::wslc::workflow
{
namespace
{
bool IsRunningAsAdmin()
{
return wil::test_token_membership(nullptr, SECURITY_NT_AUTHORITY, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS);
}
}
bool WorkflowTask::operator==(const WorkflowTask& other) const
{
if (m_isFunc && other.m_isFunc)
@ -85,20 +73,6 @@ namespace wsl::windows::wslc::workflow
{
return HandleException(&context, exception);
}
void EnsureRunningAsAdmin(CLIExecutionContext& context)
{
if (!IsRunningAsAdmin())
{
wslutil::PrintMessage(Localization::WSLCCLI_CommandRequiresAdmin(), stderr);
WSLC_TERMINATE_CONTEXT(WSLC_CLI_ERROR_COMMAND_REQUIRES_ADMIN);
}
}
void ReportExecutionStage::operator()(CLIExecutionContext& context) const
{
context.SetExecutionStage(m_stage);
}
}
CLIExecutionContext& operator<<(CLIExecutionContext& context, wsl::windows::wslc::workflow::WorkflowTask::Func f)

View File

@ -12,11 +12,6 @@ using namespace wsl::windows::wslc::execution;
namespace wsl::windows::wslc::workflow
{
enum class OperationType
{
Completion,
};
// A task in the workflow.
struct WorkflowTask
{
@ -56,38 +51,6 @@ namespace wsl::windows::wslc::workflow
// Helper to report exceptions and return the HRESULT.
HRESULT HandleException(CLIExecutionContext& context, std::exception_ptr exception);
// Ensures that the process is running as admin.
// Required Args: None
// Inputs: None
// Outputs: None
void EnsureRunningAsAdmin(CLIExecutionContext& context);
// Ensures that the process is running as admin.
// Required Args: None
// Inputs: None
// Outputs: None
void OutputNinjaCat(CLIExecutionContext& context);
// Outputs text to the context's output.
// Required Args: None
// Inputs: Text
// Outputs: None
void OutputText(CLIExecutionContext& context, std::wstring_view text);
// Reports execution stage in a workflow
// Required Args: ExecutionStage
// Inputs: ExecutionStage?
// Outputs: ExecutionStage
struct ReportExecutionStage : public WorkflowTask
{
ReportExecutionStage(ExecutionStage stage) : WorkflowTask(L"ReportExecutionStage"), m_stage(stage) {}
void operator()(CLIExecutionContext& context) const;
private:
ExecutionStage m_stage;
};
}
// Passes the context to the function if it has not been terminated; returns the context.