Initial implementation of GithubWorkflowTool

Co-authored-by: johndoe6345789 <224850594+johndoe6345789@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2025-12-27 03:03:18 +00:00
parent 7121ad38d3
commit 9b992001f3
37 changed files with 2711 additions and 1 deletions

View File

@@ -0,0 +1,41 @@
#pragma once
#include "ExecutionBackend.h"
namespace gwt {
namespace backends {
/**
* @brief Container-based execution backend (using Docker or Podman)
*/
class ContainerBackend : public ExecutionBackend {
Q_OBJECT
public:
explicit ContainerBackend(QObject* parent = nullptr);
~ContainerBackend() override;
bool executeStep(const core::WorkflowStep& step,
const QVariantMap& context) override;
bool prepareEnvironment(const QString& runsOn) override;
void cleanup() override;
private:
QString m_containerId;
QString m_containerRuntime; // "docker" or "podman"
/**
* @brief Detect available container runtime
*/
bool detectRuntime();
/**
* @brief Map GitHub runner spec to container image
*/
QString mapRunsOnToImage(const QString& runsOn) const;
};
} // namespace backends
} // namespace gwt

View File

@@ -0,0 +1,47 @@
#pragma once
#include "core/WorkflowParser.h"
#include <QObject>
#include <QString>
namespace gwt {
namespace backends {
/**
* @brief Base class for execution backends
*/
class ExecutionBackend : public QObject {
Q_OBJECT
public:
explicit ExecutionBackend(QObject* parent = nullptr);
~ExecutionBackend() override;
/**
* @brief Execute a job step
* @param step The step to execute
* @param context Execution context (env vars, working dir, etc.)
* @return true if successful
*/
virtual bool executeStep(const core::WorkflowStep& step,
const QVariantMap& context) = 0;
/**
* @brief Prepare the execution environment
* @param runsOn The runner specification (ubuntu-latest, etc.)
* @return true if successful
*/
virtual bool prepareEnvironment(const QString& runsOn) = 0;
/**
* @brief Cleanup the execution environment
*/
virtual void cleanup() = 0;
signals:
void output(const QString& text);
void error(const QString& errorMessage);
};
} // namespace backends
} // namespace gwt

View File

@@ -0,0 +1,51 @@
#pragma once
#include "ExecutionBackend.h"
namespace gwt {
namespace backends {
/**
* @brief QEMU VM-based execution backend for higher fidelity
*/
class QemuBackend : public ExecutionBackend {
Q_OBJECT
public:
explicit QemuBackend(QObject* parent = nullptr);
~QemuBackend() override;
bool executeStep(const core::WorkflowStep& step,
const QVariantMap& context) override;
bool prepareEnvironment(const QString& runsOn) override;
void cleanup() override;
private:
QString m_vmId;
QString m_qemuPath;
/**
* @brief Find QEMU executable
*/
bool detectQemu();
/**
* @brief Map GitHub runner spec to VM image
*/
QString mapRunsOnToVMImage(const QString& runsOn) const;
/**
* @brief Start the VM
*/
bool startVM(const QString& imagePath);
/**
* @brief Stop the VM
*/
void stopVM();
};
} // namespace backends
} // namespace gwt

View File

@@ -0,0 +1,44 @@
#pragma once
#include <QObject>
#include <QStringList>
#include <memory>
namespace gwt {
namespace core {
class RepoManager;
class JobExecutor;
}
namespace cli {
/**
* @brief Handles command-line interface commands
*/
class CommandHandler : public QObject {
Q_OBJECT
public:
explicit CommandHandler(QObject* parent = nullptr);
~CommandHandler() override;
/**
* @brief Execute a command with arguments
* @param args Command line arguments
* @return Exit code
*/
int execute(const QStringList& args);
private:
std::unique_ptr<core::RepoManager> m_repoManager;
std::unique_ptr<core::JobExecutor> m_executor;
void printHelp() const;
int handleClone(const QStringList& args);
int handleList(const QStringList& args);
int handleRun(const QStringList& args);
int handleWorkflows(const QStringList& args);
};
} // namespace cli
} // namespace gwt

View File

@@ -0,0 +1,58 @@
#pragma once
#include <QString>
#include <QObject>
namespace gwt {
namespace core {
/**
* @brief Manages workflow artifacts (upload/download)
*/
class ArtifactManager : public QObject {
Q_OBJECT
public:
explicit ArtifactManager(QObject* parent = nullptr);
~ArtifactManager() override;
/**
* @brief Upload an artifact
* @param name Artifact name
* @param path Path to artifact file or directory
* @param workflowId Associated workflow ID
* @return true if successful
*/
bool uploadArtifact(const QString& name,
const QString& path,
const QString& workflowId);
/**
* @brief Download an artifact
* @param name Artifact name
* @param workflowId Associated workflow ID
* @param destinationPath Where to download the artifact
* @return true if successful
*/
bool downloadArtifact(const QString& name,
const QString& workflowId,
const QString& destinationPath);
/**
* @brief List all artifacts for a workflow
* @param workflowId The workflow ID
* @return List of artifact names
*/
QStringList listArtifacts(const QString& workflowId) const;
signals:
void uploadProgress(int percentage);
void downloadProgress(int percentage);
void error(const QString& errorMessage);
private:
QString getArtifactPath(const QString& name, const QString& workflowId) const;
};
} // namespace core
} // namespace gwt

View File

@@ -0,0 +1,64 @@
#pragma once
#include <QString>
#include <QStringList>
#include <QObject>
namespace gwt {
namespace core {
/**
* @brief Manages workflow caching (actions/cache equivalent)
*/
class CacheManager : public QObject {
Q_OBJECT
public:
explicit CacheManager(QObject* parent = nullptr);
~CacheManager() override;
/**
* @brief Save paths to cache with a key
* @param key Cache key
* @param paths Paths to cache
* @return true if successful
*/
bool saveCache(const QString& key, const QStringList& paths);
/**
* @brief Restore cached paths
* @param key Cache key
* @param paths Paths to restore to
* @return true if cache hit and restored
*/
bool restoreCache(const QString& key, const QStringList& paths);
/**
* @brief Check if a cache key exists
* @param key Cache key
* @return true if exists
*/
bool hasCache(const QString& key) const;
/**
* @brief Clear all caches
*/
void clearAll();
/**
* @brief Clear specific cache entry
* @param key Cache key
*/
void clearCache(const QString& key);
signals:
void cacheHit(const QString& key);
void cacheMiss(const QString& key);
void error(const QString& errorMessage);
private:
QString getCachePath(const QString& key) const;
};
} // namespace core
} // namespace gwt

View File

@@ -0,0 +1,71 @@
#pragma once
#include "WorkflowParser.h"
#include <QObject>
#include <memory>
namespace gwt {
namespace backends {
class ExecutionBackend;
}
namespace core {
/**
* @brief Executes workflow jobs and manages their lifecycle
*/
class JobExecutor : public QObject {
Q_OBJECT
public:
explicit JobExecutor(QObject* parent = nullptr);
~JobExecutor() override;
/**
* @brief Execute a complete workflow
* @param workflow The workflow to execute
* @param triggerEvent The event that triggered the workflow
* @param useQemu Use QEMU backend instead of container backend
* @return true if execution started successfully
*/
bool executeWorkflow(const Workflow& workflow,
const QString& triggerEvent,
bool useQemu = false);
/**
* @brief Stop execution of current workflow
*/
void stopExecution();
/**
* @brief Check if execution is currently running
* @return true if running
*/
bool isRunning() const;
signals:
void jobStarted(const QString& jobId);
void jobFinished(const QString& jobId, bool success);
void stepStarted(const QString& jobId, const QString& stepName);
void stepFinished(const QString& jobId, const QString& stepName, bool success);
void stepOutput(const QString& jobId, const QString& stepName, const QString& output);
void executionFinished(bool success);
void error(const QString& errorMessage);
private:
bool m_running;
std::unique_ptr<backends::ExecutionBackend> m_backend;
/**
* @brief Execute a single job
*/
bool executeJob(const WorkflowJob& job);
/**
* @brief Resolve job dependencies (needs)
*/
QStringList resolveJobOrder(const Workflow& workflow) const;
};
} // namespace core
} // namespace gwt

View File

@@ -0,0 +1,41 @@
#pragma once
#include <QVariantMap>
#include <QList>
namespace gwt {
namespace core {
struct WorkflowJob;
/**
* @brief Handles matrix strategy expansion for workflow jobs
*/
class MatrixStrategy {
public:
MatrixStrategy();
~MatrixStrategy();
/**
* @brief Expand a job with matrix strategy into multiple jobs
* @param job The job with matrix strategy
* @return List of expanded jobs
*/
QList<WorkflowJob> expandMatrix(const WorkflowJob& job) const;
/**
* @brief Check if a job has a matrix strategy
* @param job The job to check
* @return true if matrix strategy is present
*/
bool hasMatrix(const WorkflowJob& job) const;
private:
/**
* @brief Generate all combinations from matrix variables
*/
QList<QVariantMap> generateCombinations(const QVariantMap& matrix) const;
};
} // namespace core
} // namespace gwt

View File

@@ -0,0 +1,68 @@
#pragma once
#include <QString>
#include <QStringList>
#include <QObject>
#include <memory>
namespace gwt {
namespace core {
class StorageProvider;
/**
* @brief Manages Git repository cloning and operations
*/
class RepoManager : public QObject {
Q_OBJECT
public:
explicit RepoManager(QObject* parent = nullptr);
~RepoManager() override;
/**
* @brief Clone a repository to the local storage
* @param repoUrl The repository URL (e.g., https://github.com/owner/repo)
* @param branch Optional branch to clone (default: main/master)
* @return true if successful
*/
bool cloneRepository(const QString& repoUrl, const QString& branch = QString());
/**
* @brief Update an existing repository
* @param repoUrl The repository URL
* @return true if successful
*/
bool updateRepository(const QString& repoUrl);
/**
* @brief Get the local path for a repository
* @param repoUrl The repository URL
* @return Local file system path
*/
QString getLocalPath(const QString& repoUrl) const;
/**
* @brief Check if a repository is already cloned
* @param repoUrl The repository URL
* @return true if already cloned
*/
bool isCloned(const QString& repoUrl) const;
/**
* @brief List all cloned repositories
* @return List of repository URLs
*/
QStringList listRepositories() const;
signals:
void cloneProgress(int percentage, const QString& message);
void cloneFinished(bool success);
void error(const QString& errorMessage);
private:
StorageProvider& m_storage;
};
} // namespace core
} // namespace gwt

View File

@@ -0,0 +1,66 @@
#pragma once
#include <QString>
#include <QDir>
namespace gwt {
namespace core {
/**
* @brief Provides platform-specific storage paths for repository management
*
* Windows: %APPDATA%\GithubWorkflowTool\repos\
* Linux: $XDG_DATA_HOME/githubworkflowtool/repos/ or ~/.local/share/githubworkflowtool/repos/
* Cache: $XDG_CACHE_HOME/githubworkflowtool/ or ~/.cache/githubworkflowtool/
*/
class StorageProvider {
public:
/**
* @brief Get the singleton instance
*/
static StorageProvider& instance();
/**
* @brief Get the root directory for repository storage
* @return Path to repository storage root
*/
QString getRepoStorageRoot() const;
/**
* @brief Get the cache directory
* @return Path to cache directory
*/
QString getCacheRoot() const;
/**
* @brief Get the directory for a specific repository
* @param repoUrl The repository URL
* @return Path to the repository's local storage
*/
QString getRepoDirectory(const QString& repoUrl) const;
/**
* @brief Ensure storage directories exist
* @return true if directories were created or already exist
*/
bool ensureDirectoriesExist();
private:
StorageProvider();
~StorageProvider() = default;
StorageProvider(const StorageProvider&) = delete;
StorageProvider& operator=(const StorageProvider&) = delete;
/**
* @brief Generate a normalized repository key from URL
* @param repoUrl The repository URL
* @return Normalized key (host/owner/name + hash)
*/
QString generateRepoKey(const QString& repoUrl) const;
QString m_repoRoot;
QString m_cacheRoot;
};
} // namespace core
} // namespace gwt

View File

@@ -0,0 +1,44 @@
#pragma once
#include <QString>
#include <QStringList>
#include <QObject>
namespace gwt {
namespace core {
/**
* @brief Discovers GitHub workflow files in a repository
*/
class WorkflowDiscovery : public QObject {
Q_OBJECT
public:
explicit WorkflowDiscovery(QObject* parent = nullptr);
~WorkflowDiscovery() override;
/**
* @brief Discover workflow files in a repository
* @param repoPath Local path to the repository
* @return List of workflow file paths
*/
QStringList discoverWorkflows(const QString& repoPath) const;
/**
* @brief Check if a path contains valid workflow files
* @param repoPath Local path to check
* @return true if workflows exist
*/
bool hasWorkflows(const QString& repoPath) const;
private:
/**
* @brief Validate that a file is a proper YAML workflow
* @param filePath Path to the YAML file
* @return true if valid
*/
bool isValidWorkflow(const QString& filePath) const;
};
} // namespace core
} // namespace gwt

View File

@@ -0,0 +1,84 @@
#pragma once
#include <QString>
#include <QVariantMap>
#include <QStringList>
#include <memory>
namespace gwt {
namespace core {
/**
* @brief Represents a workflow step
*/
struct WorkflowStep {
QString name;
QString id;
QString run; // Shell command
QString uses; // Action to use (e.g., actions/checkout@v3)
QVariantMap with; // Parameters for the action
QVariantMap env; // Environment variables
QString workingDirectory;
QString shell;
QString ifCondition; // Conditional execution
};
/**
* @brief Represents a workflow job
*/
struct WorkflowJob {
QString id;
QString name;
QString runsOn; // e.g., ubuntu-latest, windows-latest
QStringList needs; // Dependencies on other jobs
QVariantMap env; // Environment variables
QVariantMap outputs; // Job outputs
QList<WorkflowStep> steps;
QVariantMap strategy; // Matrix strategy
QString ifCondition; // Conditional execution
};
/**
* @brief Represents a complete workflow
*/
struct Workflow {
QString name;
QString filePath;
QVariantMap on; // Trigger events
QVariantMap env; // Global environment variables
QMap<QString, WorkflowJob> jobs;
};
/**
* @brief Parses GitHub workflow YAML files
*/
class WorkflowParser {
public:
WorkflowParser();
~WorkflowParser();
/**
* @brief Parse a workflow file
* @param filePath Path to the workflow YAML file
* @return Parsed workflow structure
*/
Workflow parse(const QString& filePath);
/**
* @brief Check if the last parse had errors
* @return true if errors occurred
*/
bool hasErrors() const;
/**
* @brief Get error messages from last parse
* @return List of error messages
*/
QStringList getErrors() const;
private:
QStringList m_errors;
};
} // namespace core
} // namespace gwt

25
include/gui/JobView.h Normal file
View File

@@ -0,0 +1,25 @@
#pragma once
#include <QWidget>
namespace gwt {
namespace gui {
/**
* @brief Widget for displaying job execution details
*/
class JobView : public QWidget {
Q_OBJECT
public:
explicit JobView(QWidget* parent = nullptr);
~JobView() override;
void setJobInfo(const QString& jobId, const QString& status);
private:
void setupUI();
};
} // namespace gui
} // namespace gwt

52
include/gui/MainWindow.h Normal file
View File

@@ -0,0 +1,52 @@
#pragma once
#include <QMainWindow>
#include <memory>
class QTreeWidget;
class QTextEdit;
class QPushButton;
class QComboBox;
namespace gwt {
namespace core {
class RepoManager;
class JobExecutor;
}
namespace gui {
/**
* @brief Main window for the GUI application
*/
class MainWindow : public QMainWindow {
Q_OBJECT
public:
explicit MainWindow(QWidget* parent = nullptr);
~MainWindow() override;
private slots:
void onCloneRepository();
void onRefreshRepositories();
void onRepositorySelected();
void onRunWorkflow();
void onJobOutput(const QString& jobId, const QString& stepName, const QString& output);
private:
void setupUI();
void loadRepositories();
QTreeWidget* m_repoTree;
QTreeWidget* m_workflowTree;
QTextEdit* m_outputView;
QPushButton* m_cloneButton;
QPushButton* m_runButton;
QComboBox* m_backendCombo;
std::unique_ptr<core::RepoManager> m_repoManager;
std::unique_ptr<core::JobExecutor> m_executor;
};
} // namespace gui
} // namespace gwt

View File

@@ -0,0 +1,25 @@
#pragma once
#include <QWidget>
namespace gwt {
namespace gui {
/**
* @brief Widget for displaying workflow information
*/
class WorkflowView : public QWidget {
Q_OBJECT
public:
explicit WorkflowView(QWidget* parent = nullptr);
~WorkflowView() override;
void loadWorkflow(const QString& workflowPath);
private:
void setupUI();
};
} // namespace gui
} // namespace gwt