commit e6e5bd32b5647bf8398e57e78eb82299e3ec623f Author: liyp Date: Tue Aug 25 22:12:29 2026 -0400 first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..063baec --- /dev/null +++ b/.gitignore @@ -0,0 +1,19 @@ +# Build artifacts +*.o +moc_*.cpp +moc_predefs.h +qrc_*.cpp +Makefile +VoiletCStudio +.qmake.stash + +# IDE +.vscode/ +.idea/ +*.user +*.pro.user + +# Installer output +*.exe +VoiletCStudio-Setup-*.exe +installer/build/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..c75561f --- /dev/null +++ b/README.md @@ -0,0 +1,182 @@ +# VoiletCStudio - 紫罗兰 C 工具箱 + +Qt5 跨平台 C 工程配置器,自动生成 CMakeLists.txt + +> 版本:v1.1 · 2026-04-28 + +--- + +## 🚀 功能特性 + +### 1. 工程配置管理 +- ✅ JSON 格式配置文件 +- ✅ 新建/打开/保存工程 +- ✅ 拖拽 JSON 文件打开 +- ✅ Ctrl+S 快速保存 + +### 2. 编译工具链配置 +- ✅ 编译器路径选择 +- ✅ 汇编器路径选择 +- ✅ 链接器路径选择 +- ✅ 支持 MinGW / GCC / Clang +- ✅ Windows 下自动识别 MinGW 工具链 + +### 3. 文件管理 +- ✅ 虚拟目录(类似 MDK) +- ✅ .c 源文件分类管理 +- ✅ .h 包含目录管理 +- ✅ 库文件管理(.a/.so/.lib) + +### 4. 编译配置 +- ✅ 编译宏定义 +- ✅ 自定义编译选项 +- ✅ Debug / Release 模式 + +### 5. CMake 生成 +- ✅ 自动生成 CMakeLists.txt +- ✅ 编译器路径在 `project()` 之前设置,避免默认回退到 NMake +- ✅ Windows 下自动指定 `-G "MinGW Makefiles"` 生成器 +- ✅ 内置一键编译(Debug / Release) + +--- + +## 📋 使用方法 + +### 编译 VoiletCStudio + +**Linux:** +```bash +cd VoiletCStudio +qmake +make +``` + +**Windows (MSYS2/MinGW):** +```bash +cd VoiletCStudio +qmake +mingw32-make +``` + +### 运行 +```bash +./VoiletCStudio # Linux +VoiletCStudio.exe # Windows +``` + +### 使用流程 +1. 新建工程 → 输入工程名 +2. 配置编译器路径(Windows 选 `gcc.exe`) +3. 添加虚拟目录和 .c 源文件 +4. 添加包含目录和库文件 +5. 添加编译宏和选项 +6. 保存工程(JSON) +7. 点击「生成 CMake」→ 自动生成 CMakeLists.txt +8. 点击「编译 Debug / Release」→ 一键编译 + +--- + +## 🛠️ CMake 使用 + +```bash +# Linux / macOS +cmake -B build +make debug # 编译 Debug +make release # 编译 Release + +# Windows (MinGW) +cmake -G "MinGW Makefiles" -B build +mingw32-make debug +mingw32-make release +``` + +--- + +## 📁 项目结构 + +``` +VoiletCStudio/ +├── src/ +│ ├── main.cpp # 主程序入口 +│ ├── mainwindow.cpp/h # 主窗口(含编译流程) +│ ├── projectconfig.cpp/h # 配置管理 +│ └── cmakegenerator.cpp/h # CMake 生成 +├── VoiletCStudio.pro # Qt 项目文件 +├── README.md # 说明文档 +└── 需求规格说明书.md # 详细需求文档 +``` + +--- + +## 💻 跨平台支持 + +| 平台 | 编译器 | 生成器 | 状态 | +|------|--------|--------|------| +| Windows | MinGW (MSYS2) | MinGW Makefiles | ✅ | +| Windows | MSVC | NMake | ⚠️ 未测试 | +| Linux | GCC | Unix Makefiles | ✅ | +| macOS | Clang | Unix Makefiles | ✅ | + +### Windows 特别注意 +- 必须安装 **MSYS2** 并安装 `mingw-w64-gcc`、`mingw-w64-cmake` +- 编译器路径示例:`C:/msys2/mingw64/bin/gcc.exe` +- CMake 生成时程序自动使用 `-G "MinGW Makefiles"` +- 构建时使用 `mingw32-make` 而非 `make` + +--- + +## 📝 配置文件格式 + +```json +{ + "projectName": "MyProject", + "projectPath": "/path/to/project", + "outputDir": "./build", + "compilerPath": "/usr/bin/gcc", + "assemblerPath": "/usr/bin/gcc", + "linkerPath": "/usr/bin/gcc", + "virtualDirs": { + "App": { + "name": "App", + "files": ["src/main.c"] + } + }, + "includeDirs": ["./include"], + "libraries": ["libmylib.a"], + "defines": ["DEBUG"], + "compilerOptions": ["-Wall"] +} +``` + +--- + +## 🔧 技术要点 + +### 编译器设置顺序 +CMakeLists.txt 中 `CMAKE_C_COMPILER` **必须在 `project()` 之前设置**,否则 CMake 会在 `project()` 时自动检测编译器并可能回退到不存在的 NMake。 + +### Windows 生成器 +程序通过 `#ifdef Q_OS_WIN32` 编译期检测平台,在 Windows 上自动: +- CMake 添加 `-G "MinGW Makefiles"` 参数 +- 构建时使用 `mingw32-make` 替代 `make` +- 自动从编译器路径推导 `CMAKE_MAKE_PROGRAM` + +--- + +## 🎯 特色 + +- **类 MDK 虚拟目录**:像 Keil MDK 一样管理源文件 +- **一键生成 CMake**:自动生成完整的 CMakeLists.txt +- **真正跨平台**:Windows (MinGW) / Linux / macOS 全支持,自动化平台适配 +- **轻量级**:纯 Qt5 实现,无额外依赖 + +--- + +## 📄 许可证 + +MIT License + +--- + +**作者:虾哥** +**日期:2026-04-09 · 更新:2026-04-28** diff --git a/VoiletCStudio.pro b/VoiletCStudio.pro new file mode 100644 index 0000000..a0ffc2e --- /dev/null +++ b/VoiletCStudio.pro @@ -0,0 +1,30 @@ +QT += core gui widgets + +greaterThan(QT_MAJOR_VERSION, 4): QT += widgets + +CONFIG += c++11 + +TARGET = VoiletCStudio +TEMPLATE = app + +# 源文件 +SOURCES += \ + src/main.cpp \ + src/mainwindow.cpp \ + src/projectconfig.cpp \ + src/cmakegenerator.cpp + +# 头文件 +HEADERS += \ + src/mainwindow.h \ + src/projectconfig.h \ + src/cmakegenerator.h + +# 资源文件 +RESOURCES += \ + resources.qrc + +# 默认部署规则 +qnx: target.path = /tmp/$${TARGET}/bin +else: unix:!android: target.path = /opt/$${TARGET}/bin +!isEmpty(target.path): INSTALLS += target diff --git a/installer/install.sh b/installer/install.sh new file mode 100644 index 0000000..e626e5b --- /dev/null +++ b/installer/install.sh @@ -0,0 +1,92 @@ +#!/bin/bash +# ===================================================== +# VoiletCStudio Linux 安装脚本 +# 用法: sudo bash installer/install.sh +# ===================================================== + +set -e + +APP_NAME="VoiletCStudio" +VERSION="1.2" +INSTALL_DIR="/opt/${APP_NAME}" +BIN_DIR="/usr/local/bin" +DESKTOP_DIR="/usr/share/applications" +MIME_DIR="/usr/share/mime/packages" +ICON_DIR="/usr/share/icons/hicolor/256x256/apps" + +echo "=========================================" +echo " VoiletCStudio v${VERSION} - Linux 安装程序" +echo "=========================================" + +# 检查 root +if [ "$EUID" -ne 0 ]; then + echo "请使用 sudo 运行: sudo bash installer/install.sh" + exit 1 +fi + +echo "" +echo "[1/5] 编译程序..." +mkdir -p build && cd build +qmake ../VoiletCStudio.pro && make -j$(nproc) +cd .. + +echo "" +echo "[2/5] 安装到 ${INSTALL_DIR}..." +mkdir -p "${INSTALL_DIR}" +cp build/VoiletCStudio "${INSTALL_DIR}/" +chmod +x "${INSTALL_DIR}/VoiletCStudio" + +# 创建命令行软链接 +ln -sf "${INSTALL_DIR}/VoiletCStudio" "${BIN_DIR}/voiletcstudio" + +echo "" +echo "[3/5] 安装图标..." +mkdir -p "${ICON_DIR}" +cp resources/voiletcstudio.png "${ICON_DIR}/voiletcstudio.png" + +echo "" +echo "[4/5] 注册 MIME 类型和 .desktop..." +mkdir -p "${MIME_DIR}" +cat > "${MIME_DIR}/voiletcstudio.xml" << 'MIMEXML' + + + + VoiletCStudio Project + VoiletCStudio 工程文件 + + + + +MIMEXML + +mkdir -p "${DESKTOP_DIR}" +cat > "${DESKTOP_DIR}/voiletcstudio.desktop" << DESKTOPEOF +[Desktop Entry] +Type=Application +Name=VoiletCStudio +Name[zh_CN]=紫罗兰 C 工程配置器 +Comment=C Project Configurator with CMake +Comment[zh_CN]=C 语言工程配置器,自动生成 CMakeLists.txt +Exec=${INSTALL_DIR}/VoiletCStudio %f +Icon=voiletcstudio +MimeType=application/x-voiletcstudio; +Categories=Development;IDE; +Terminal=false +DESKTOPEOF + +echo "" +echo "[5/5] 更新系统数据库..." +update-mime-database /usr/share/mime +update-desktop-database +gtk-update-icon-cache /usr/share/icons/hicolor 2>/dev/null || true + +echo "" +echo "=========================================" +echo " ✅ VoiletCStudio 安装完成!" +echo "=========================================" +echo "" +echo " 命令行: voiletcstudio" +echo " 或双击 .VSC 工程文件自动打开" +echo "" +echo " 卸载: sudo bash installer/uninstall.sh" +echo "" diff --git a/installer/uninstall.sh b/installer/uninstall.sh new file mode 100644 index 0000000..438342e --- /dev/null +++ b/installer/uninstall.sh @@ -0,0 +1,25 @@ +#!/bin/bash +# ===================================================== +# VoiletCStudio Linux 卸载脚本 +# ===================================================== +set -e + +if [ "$EUID" -ne 0 ]; then + echo "请使用 sudo 运行" + exit 1 +fi + +APP_NAME="VoiletCStudio" + +echo "正在卸载 ${APP_NAME}..." + +rm -rf "/opt/${APP_NAME}" +rm -f "/usr/local/bin/voiletcstudio" +rm -f "/usr/share/applications/voiletcstudio.desktop" +rm -f "/usr/share/mime/packages/voiletcstudio.xml" +rm -f "/usr/share/icons/hicolor/256x256/apps/voiletcstudio.png" + +update-mime-database /usr/share/mime 2>/dev/null || true +update-desktop-database 2>/dev/null || true + +echo "✅ ${APP_NAME} 已卸载" diff --git a/installer/voiletcstudio.nsi b/installer/voiletcstudio.nsi new file mode 100644 index 0000000..922ecf6 --- /dev/null +++ b/installer/voiletcstudio.nsi @@ -0,0 +1,85 @@ +; ===================================================== +; VoiletCStudio Windows 安装脚本 (NSIS) +; 用法: makensis installer/voiletcstudio.nsi +; ===================================================== + +!define PRODUCT_NAME "VoiletCStudio" +!define PRODUCT_VERSION "1.2" +!define PRODUCT_PUBLISHER "LinuxAcme" +!define PRODUCT_EXE "VoiletCStudio.exe" + +Name "${PRODUCT_NAME} ${PRODUCT_VERSION}" +OutFile "VoiletCStudio-Setup-${PRODUCT_VERSION}.exe" +InstallDir "$PROGRAMFILES\${PRODUCT_NAME}" +RequestExecutionLevel admin + +; ----- 安装页面 ----- +Page directory +Page instfiles + +; ----- 默认安装路径 ----- +Section "Install" + SetOutPath "$INSTDIR" + + ; 复制主程序 + File "VoiletCStudio.exe" + + ; 复制 Qt 运行时 DLL(需要提前准备好) + ; File /r "Qt5Core.dll" + ; File /r "Qt5Gui.dll" + ; File /r "Qt5Widgets.dll" + + ; 复制 MinGW 运行时 + ; File /r "libgcc_s_seh-1.dll" + ; File /r "libstdc++-6.dll" + ; File /r "libwinpthread-1.dll" + + ; 创建卸载程序 + WriteUninstaller "$INSTDIR\uninstall.exe" + + ; 注册 .VSC 文件关联 + WriteRegStr HKCR ".VSC" "" "VoiletCStudio.VSC" + WriteRegStr HKCR "VoiletCStudio.VSC" "" "VoiletCStudio 工程文件" + WriteRegStr HKCR "VoiletCStudio.VSC\DefaultIcon" "" "$INSTDIR\${PRODUCT_EXE},0" + WriteRegStr HKCR "VoiletCStudio.VSC\shell\open\command" "" '"$INSTDIR\${PRODUCT_EXE}" "%1"' + + ; 注册表卸载信息 + WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" "DisplayName" "${PRODUCT_NAME}" + WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" "UninstallString" "$INSTDIR\uninstall.exe" + WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" "DisplayVersion" "${PRODUCT_VERSION}" + WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" "Publisher" "${PRODUCT_PUBLISHER}" + + ; 开始菜单快捷方式 + CreateDirectory "$SMPROGRAMS\${PRODUCT_NAME}" + CreateShortCut "$SMPROGRAMS\${PRODUCT_NAME}\VoiletCStudio.lnk" "$INSTDIR\${PRODUCT_EXE}" + CreateShortCut "$SMPROGRAMS\${PRODUCT_NAME}\卸载.lnk" "$INSTDIR\uninstall.exe" + + ; 桌面快捷方式 + CreateShortCut "$DESKTOP\VoiletCStudio.lnk" "$INSTDIR\${PRODUCT_EXE}" + + ; 通知系统刷新文件关联 + System::Call 'shell32.dll::SHChangeNotify(i, i, i, i) v (0x08000000, 0, 0, 0)' +SectionEnd + +; ----- 卸载 ----- +Section "Uninstall" + ; 删除程序 + Delete "$INSTDIR\${PRODUCT_EXE}" + Delete "$INSTDIR\uninstall.exe" + RMDir "$INSTDIR" + + ; 删除文件关联 + DeleteRegKey HKCR ".VSC" + DeleteRegKey HKCR "VoiletCStudio.VSC" + + ; 删除注册表 + DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" + + ; 删除快捷方式 + Delete "$SMPROGRAMS\${PRODUCT_NAME}\VoiletCStudio.lnk" + Delete "$SMPROGRAMS\${PRODUCT_NAME}\卸载.lnk" + RMDir "$SMPROGRAMS\${PRODUCT_NAME}" + Delete "$DESKTOP\VoiletCStudio.lnk" + + System::Call 'shell32.dll::SHChangeNotify(i, i, i, i) v (0x08000000, 0, 0, 0)' +SectionEnd diff --git a/resources.qrc b/resources.qrc new file mode 100644 index 0000000..c79246c --- /dev/null +++ b/resources.qrc @@ -0,0 +1,6 @@ + + + resources/voiletcstudio.png + resources/voiletcstudio.ico + + diff --git a/resources/voiletcstudio.desktop b/resources/voiletcstudio.desktop new file mode 100644 index 0000000..f0e987a --- /dev/null +++ b/resources/voiletcstudio.desktop @@ -0,0 +1,11 @@ +[Desktop Entry] +Type=Application +Name=VoiletCStudio +Name[zh_CN]=紫罗兰 C 工程配置器 +Comment=C Project Configurator with CMake +Comment[zh_CN]=C 语言工程配置器,自动生成 CMakeLists.txt +Exec=VoiletCStudio %f +Icon=voiletcstudio +MimeType=application/x-voiletcstudio; +Categories=Development;IDE; +Terminal=false diff --git a/resources/voiletcstudio.ico b/resources/voiletcstudio.ico new file mode 100644 index 0000000..70de32d Binary files /dev/null and b/resources/voiletcstudio.ico differ diff --git a/resources/voiletcstudio.png b/resources/voiletcstudio.png new file mode 100644 index 0000000..48ae1d2 Binary files /dev/null and b/resources/voiletcstudio.png differ diff --git a/resources/voiletcstudio.svg b/resources/voiletcstudio.svg new file mode 100644 index 0000000..98e28fe --- /dev/null +++ b/resources/voiletcstudio.svg @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/run.sh b/run.sh new file mode 100755 index 0000000..efd3364 --- /dev/null +++ b/run.sh @@ -0,0 +1,3 @@ +#!/bin/bash +cd "$(dirname "$0")" +./VoiletCStudio diff --git a/src/cmakegenerator.cpp b/src/cmakegenerator.cpp new file mode 100644 index 0000000..7e7bd6e --- /dev/null +++ b/src/cmakegenerator.cpp @@ -0,0 +1,316 @@ +#include "cmakegenerator.h" +#include +#include +#include +#include +#include + +CMakeGenerator::CMakeGenerator(QObject *parent) + : QObject(parent) +{ +} + +bool CMakeGenerator::generate(const ProjectConfig *config, const QString &outputPath) +{ + if (!config) { + emit errorOccurred("配置对象为空"); + return false; + } + + QString content = generateContent(config); + + QFileInfo fileInfo(outputPath); + QDir dir = fileInfo.absoluteDir(); + if (!dir.exists()) { + dir.mkpath("."); + } + + QFile file(outputPath); + if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { + emit errorOccurred("无法创建 CMakeLists.txt: " + file.errorString()); + return false; + } + + QTextStream out(&file); + out.setCodec("UTF-8"); + out << content; + file.close(); + + m_lastGeneratedPath = outputPath; + emit cmakeGenerated(outputPath); + + return true; +} + +QString CMakeGenerator::getLastGeneratedPath() const +{ + return m_lastGeneratedPath; +} + +QString CMakeGenerator::generateContent(const ProjectConfig *config) +{ + QString content; + + content += generateHeader(config); + content += "\n"; + content += generateProjectConfig(config); + content += "\n"; + content += generateSourceFiles(config); + content += "\n"; + content += generateIncludeDirs(config); + content += "\n"; + content += generateLibraries(config); + content += "\n"; + content += generateDefines(config); + content += "\n"; + content += generateCompilerOptions(config); + content += "\n"; + content += generateBuildTargets(config); + + return content; +} + +QString CMakeGenerator::generateHeader(const ProjectConfig *config) +{ + QString header; + header += "# ========================================\n"; + header += "# CMakeLists.txt - VoiletCStudio 自动生成\n"; + header += "# 工程名称:" + config->getProjectName() + "\n"; + header += "# 生成时间:" + QDateTime::currentDateTime().toString("yyyy-MM-dd HH:mm:ss") + "\n"; + header += "# 跨平台支持:Windows (MinGW) / Linux (GCC) / macOS (Clang)\n"; + header += "# ========================================\n\n"; + + header += "cmake_minimum_required(VERSION 3.10)\n\n"; + + return header; +} + +QString CMakeGenerator::generateProjectConfig(const ProjectConfig *config) +{ + QString content; + + QString projectName = config->getProjectName(); + if (projectName.isEmpty()) { + projectName = "UntitledProject"; + } + + // ===== 编译器必须在 project() 之前设置,否则 CMake 会默认用 NMake ===== + content += "# ========================================\n"; + content += "# 编译器配置(必须在 project() 之前)\n"; + content += "# ========================================\n\n"; + + QString compilerPath = config->getCompilerPath(); + QString assemblerPath = config->getAssemblerPath(); + QString linkerPath = config->getLinkerPath(); + + if (!compilerPath.isEmpty() && compilerPath != "gcc") { + content += "set(CMAKE_C_COMPILER \"" + compilerPath + "\")\n"; + + // 如果编译器是 MinGW 路径,自动推导 make 程序目录 + // 例如:C:/msys2/mingw64/bin/gcc.exe → C:/msys2/mingw64/bin/mingw32-make.exe + if (compilerPath.contains("mingw", Qt::CaseInsensitive)) { + QString makeDir = QFileInfo(compilerPath).path(); // path() 正确保留绝对路径的盘符 + content += "set(CMAKE_MAKE_PROGRAM \"" + makeDir + "/mingw32-make.exe\")\n"; + } + } + + if (!assemblerPath.isEmpty() && assemblerPath != "gcc") { + content += "set(CMAKE_ASM_COMPILER \"" + assemblerPath + "\")\n"; + } + + if (!linkerPath.isEmpty() && linkerPath != "gcc" && linkerPath != compilerPath) { + content += "set(CMAKE_C_LINK_EXECUTABLE \"" + linkerPath + " -o \")\n"; + } + content += "\n"; + + content += "# 项目名称(编译器已设置,不再尝试检测)\n"; + content += "project(" + projectName + " C)\n\n"; + + content += "# C 标准\n"; + content += "set(CMAKE_C_STANDARD 11)\n"; + content += "set(CMAKE_C_STANDARD_REQUIRED ON)\n\n"; + + // 输出目录配置 + QString outputDir = config->getOutputDir(); + if (outputDir.isEmpty()) { + outputDir = "./build"; + } + + content += "# 输出目录配置\n"; + content += "set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/" + outputDir + ")\n"; + content += "set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/" + outputDir + ")\n"; + content += "set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/" + outputDir + ")\n\n"; + + return content; +} + +QString CMakeGenerator::generateSourceFiles(const ProjectConfig *config) +{ + QString content; + content += "# ========================================\n"; + content += "# 源文件配置(虚拟目录结构)\n"; + content += "# ========================================\n\n"; + + QMap virtualDirs = config->getVirtualDirs(); + + if (virtualDirs.isEmpty()) { + content += "# 无源文件\n"; + } else { + // 遍历虚拟目录 + for (auto it = virtualDirs.begin(); it != virtualDirs.end(); ++it) { + content += "# 分组:" + it.key() + "\n"; + QString safeName = it.key(); + safeName.replace(" ", "_"); + safeName.replace("/", "_"); + content += "set(SRCS_" + safeName + "\n"; + for (const QString &file : it->files) { + content += " " + file + "\n"; + } + content += ")\n\n"; + } + + // 合并所有源文件 + content += "# 合并所有源文件\n"; + content += "set(SOURCES\n"; + for (auto it = virtualDirs.begin(); it != virtualDirs.end(); ++it) { + for (const QString &file : it->files) { + content += " " + file + "\n"; + } + } + content += ")\n\n"; + } + + content += "# 创建可执行文件\n"; + QString outputName = config->getProjectName(); + if (outputName.isEmpty()) { + outputName = "output"; + } + + content += "add_executable(" + outputName + " ${SOURCES})\n\n"; + + return content; +} + +QString CMakeGenerator::generateIncludeDirs(const ProjectConfig *config) +{ + QString content; + content += "# 包含目录\n"; + + QStringList includeDirs = config->getIncludeDirs(); + if (includeDirs.isEmpty()) { + content += "# 无额外包含目录\n"; + } else { + content += "target_include_directories(" + config->getProjectName() + " PRIVATE\n"; + for (const QString &dir : includeDirs) { + content += " " + dir + "\n"; + } + content += ")\n"; + } + + return content; +} + +QString CMakeGenerator::generateLibraries(const ProjectConfig *config) +{ + QString content; + content += "# 链接库\n"; + + QStringList libraries = config->getLibraries(); + if (libraries.isEmpty()) { + content += "# 无外部库文件\n"; + } else { + content += "target_link_libraries(" + config->getProjectName() + " PRIVATE\n"; + for (const QString &lib : libraries) { + content += " " + lib + "\n"; + } + content += ")\n"; + } + + return content; +} + +QString CMakeGenerator::generateDefines(const ProjectConfig *config) +{ + QString content; + content += "# 预编译宏定义\n"; + + QStringList defines = config->getDefines(); + if (defines.isEmpty()) { + content += "# 无额外宏定义\n"; + } else { + content += "target_compile_definitions(" + config->getProjectName() + " PRIVATE\n"; + for (const QString &def : defines) { + content += " " + def + "\n"; + } + content += ")\n"; + } + + return content; +} + +QString CMakeGenerator::generateCompilerOptions(const ProjectConfig *config) +{ + QString content; + content += "# 自定义编译选项\n"; + + QStringList options = config->getCompilerOptions(); + if (options.isEmpty()) { + content += "# 无自定义编译选项\n"; + } else { + content += "target_compile_options(" + config->getProjectName() + " PRIVATE\n"; + for (const QString &opt : options) { + content += " " + opt + "\n"; + } + content += ")\n"; + } + + return content; +} + +QString CMakeGenerator::generateBuildTargets(const ProjectConfig * /*config*/) +{ + QString content; + + content += "# ========================================\n"; + content += "# 构建类型配置\n"; + content += "# ========================================\n\n"; + + content += "# 默认构建类型:Debug\n"; + content += "if(NOT CMAKE_BUILD_TYPE)\n"; + content += " set(CMAKE_BUILD_TYPE Debug CACHE STRING \"Build type\" FORCE)\n"; + content += "endif()\n\n"; + + content += "# Debug 模式配置(自动添加 DEBUG 宏)\n"; + content += "set(CMAKE_C_FLAGS_DEBUG \"-g -O0 -DDEBUG\" CACHE STRING \"Debug flags\" FORCE)\n\n"; + + content += "# Release 模式配置\n"; + content += "set(CMAKE_C_FLAGS_RELEASE \"-O2 -DNDEBUG\" CACHE STRING \"Release flags\" FORCE)\n\n"; + + content += "# ========================================\n"; + content += "# 自定义 Make 目标:make debug / make release\n"; + content += "# ========================================\n\n"; + + content += "# make debug - 编译 Debug 版本(自动添加 DEBUG 宏)\n"; + content += "add_custom_target(debug\n"; + content += " COMMAND ${CMAKE_COMMAND} -DCMAKE_BUILD_TYPE=Debug ${CMAKE_SOURCE_DIR}\n"; + content += " COMMAND ${CMAKE_COMMAND} --build ${CMAKE_BINARY_DIR} --config Debug\n"; + content += " COMMENT \"Building Debug version\"\n"; + content += " VERBATIM\n"; + content += ")\n\n"; + + content += "# make release - 编译 Release 版本\n"; + content += "add_custom_target(release\n"; + content += " COMMAND ${CMAKE_COMMAND} -DCMAKE_BUILD_TYPE=Release ${CMAKE_SOURCE_DIR}\n"; + content += " COMMAND ${CMAKE_COMMAND} --build ${CMAKE_BINARY_DIR} --config Release\n"; + content += " COMMENT \"Building Release version\"\n"; + content += " VERBATIM\n"; + content += ")\n\n"; + + content += "# 使用说明:\n"; + content += "# cmake -B build # 配置(默认 Debug)\n"; + content += "# cmake --build build # 编译(使用当前 CMAKE_BUILD_TYPE)\n"; + content += "# make debug # 编译 Debug 版本(自动添加 DEBUG 宏)\n"; + content += "# make release # 编译 Release 版本\n"; + + return content; +} diff --git a/src/cmakegenerator.h b/src/cmakegenerator.h new file mode 100644 index 0000000..e56ec81 --- /dev/null +++ b/src/cmakegenerator.h @@ -0,0 +1,39 @@ +#ifndef CMAKEGENERATOR_H +#define CMAKEGENERATOR_H + +#include +#include +#include "projectconfig.h" + +class CMakeGenerator : public QObject +{ + Q_OBJECT + +public: + explicit CMakeGenerator(QObject *parent = nullptr); + + // 生成 CMakeLists.txt + bool generate(const ProjectConfig *config, const QString &outputPath); + + // 获取最后生成的路径 + QString getLastGeneratedPath() const; + +signals: + void cmakeGenerated(const QString &path); + void errorOccurred(const QString &error); + +private: + QString generateContent(const ProjectConfig *config); + QString generateHeader(const ProjectConfig *config); + QString generateProjectConfig(const ProjectConfig *config); + QString generateSourceFiles(const ProjectConfig *config); + QString generateIncludeDirs(const ProjectConfig *config); + QString generateLibraries(const ProjectConfig *config); + QString generateDefines(const ProjectConfig *config); + QString generateCompilerOptions(const ProjectConfig *config); + QString generateBuildTargets(const ProjectConfig *config); + + QString m_lastGeneratedPath; +}; + +#endif // CMAKEGENERATOR_H diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..bd99db9 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,177 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "mainwindow.h" + +#ifdef Q_OS_WIN32 +#include +#include +#endif + +// ===== 文件关联注册 ===== +static void registerFileAssociation() +{ +#ifdef Q_OS_WIN32 + QString appPath = QCoreApplication::applicationFilePath(); + appPath.replace("/", "\\"); + + // 注册 .VSC 扩展名 + QSettings settings("HKEY_CLASSES_ROOT\\.VSC", QSettings::NativeFormat); + settings.setValue("Default", "VoiletCStudio.VSC"); + settings.sync(); + + // 注册文件类型描述 + QSettings typeSettings("HKEY_CLASSES_ROOT\\VoiletCStudio.VSC", QSettings::NativeFormat); + typeSettings.setValue("Default", "VoiletCStudio 工程文件"); + typeSettings.sync(); + + // 注册图标 + QSettings iconSettings("HKEY_CLASSES_ROOT\\VoiletCStudio.VSC\\DefaultIcon", QSettings::NativeFormat); + iconSettings.setValue("Default", appPath + ",0"); + iconSettings.sync(); + + // 注册打开命令 + QSettings cmdSettings("HKEY_CLASSES_ROOT\\VoiletCStudio.VSC\\shell\\open\\command", QSettings::NativeFormat); + cmdSettings.setValue("Default", "\"" + appPath + "\" \"%1\""); + cmdSettings.sync(); + + // 通知系统刷新 + SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, nullptr, nullptr); + + qDebug() << "[OK] .VSC file association registered on Windows"; +#else + // Linux: 写入 MIME 类型定义 + 更新 desktop 数据库 + QString mimeDir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/mime/packages"; + QDir().mkpath(mimeDir); + + QString mimeFile = mimeDir + "/voiletcstudio.xml"; + QFile f(mimeFile); + if (f.open(QIODevice::WriteOnly)) { + f.write(R"( + + + VoiletCStudio Project + VoiletCStudio 工程文件 + + + + +)"); + f.close(); + } + + // 写入 .desktop 文件 + QString appsDir = QStandardPaths::writableLocation(QStandardPaths::ApplicationsLocation); + QDir().mkpath(appsDir); + + QString desktopFile = appsDir + "/voiletcstudio.desktop"; + QFile df(desktopFile); + if (df.open(QIODevice::WriteOnly)) { + df.write(R"([Desktop Entry] +Type=Application +Name=VoiletCStudio +Name[zh_CN]=紫罗兰 C 工程配置器 +Comment=C Project Configurator +Comment[zh_CN]=C 语言工程配置器 +Exec=)" + QCoreApplication::applicationFilePath().toUtf8() + R"( %f +Icon=voiletcstudio +MimeType=application/x-voiletcstudio; +Categories=Development;IDE; +Terminal=false +)"); + df.close(); + } + + // 更新 MIME 数据库 + QProcess::execute("update-mime-database", QStringList() << + QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/mime"); + QProcess::execute("update-desktop-database", QStringList()); + + qDebug() << "[OK] .VSC file association registered on Linux"; +#endif +} + +static void unregisterFileAssociation() +{ +#ifdef Q_OS_WIN32 + QSettings settings("HKEY_CLASSES_ROOT\\.VSC", QSettings::NativeFormat); + settings.remove("Default"); + settings.sync(); + + QSettings("HKEY_CLASSES_ROOT\\VoiletCStudio.VSC", QSettings::NativeFormat).remove(""); + SHDeleteKey(HKEY_CLASSES_ROOT, L"VoiletCStudio.VSC"); + SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, nullptr, nullptr); + + qDebug() << "[OK] .VSC file association removed"; +#else + QString mimeFile = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/mime/packages/voiletcstudio.xml"; + QFile::remove(mimeFile); + QProcess::execute("update-mime-database", QStringList() << + QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/mime"); + qDebug() << "[OK] .VSC file association removed"; +#endif +} + +int main(int argc, char *argv[]) +{ + QApplication app(argc, argv); + + // 设置应用图标 + app.setWindowIcon(QIcon(":/resources/voiletcstudio.png")); + + // 设置应用程序信息 + app.setApplicationName("VoiletCStudio"); + app.setApplicationVersion("1.2"); + app.setOrganizationName("LinuxAcme"); + + // 命令行参数解析 + QCommandLineParser parser; + parser.setApplicationDescription("紫罗兰 C 工程配置器 - 自动生成 CMakeLists.txt"); + parser.addHelpOption(); + parser.addVersionOption(); + + QCommandLineOption registerOpt("register", "注册 .VSC 文件关联到系统"); + QCommandLineOption unregisterOpt("unregister", "取消 .VSC 文件关联"); + parser.addOption(registerOpt); + parser.addOption(unregisterOpt); + + // 接受 .VSC 文件作为位置参数(双击打开) + parser.addPositionalArgument("file", "要打开的 .VSC 工程文件"); + + parser.process(app); + + // 处理注册/取消注册 + if (parser.isSet(registerOpt)) { + registerFileAssociation(); + return 0; + } + if (parser.isSet(unregisterOpt)) { + unregisterFileAssociation(); + return 0; + } + + // 设置样式 + app.setStyle(QStyleFactory::create("Fusion")); + + MainWindow window; + window.show(); + + // 通过命令行传入的文件自动打开 + QStringList posArgs = parser.positionalArguments(); + if (!posArgs.isEmpty()) { + QString filePath = posArgs.first(); + if (filePath.endsWith(".VSC", Qt::CaseInsensitive) || filePath.endsWith(".json", Qt::CaseInsensitive)) { + window.openProjectFile(filePath); + } + } + + return app.exec(); +} diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp new file mode 100644 index 0000000..11dd2f9 --- /dev/null +++ b/src/mainwindow.cpp @@ -0,0 +1,922 @@ +#include "mainwindow.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +MainWindow::MainWindow(QWidget *parent) + : QMainWindow(parent) + , modified(false) + , showCMakeSuccessMsg(true) +{ + config = new ProjectConfig(this); + cmakeGenerator = new CMakeGenerator(this); + + setupUI(); + setupConnections(); + updateWindowTitle(); +} + +MainWindow::~MainWindow() +{ +} + +void MainWindow::setupUI() +{ + setWindowTitle("VoiletCStudio - C 工程配置器"); + resize(1400, 900); + + // 创建操作按钮栏 + QGroupBox *actionGroup = new QGroupBox("⚡ 快捷操作"); + QHBoxLayout *actionLayout = new QHBoxLayout(actionGroup); + + QPushButton *openBtn = new QPushButton("📂 打开工程"); + openBtn->setMinimumHeight(35); + openBtn->setStyleSheet("QPushButton { background: #1565C0; color: white; font-weight: bold; border: none; border-radius: 4px; padding: 6px 16px; }"); + connect(openBtn, &QPushButton::clicked, this, &MainWindow::openProject); + actionLayout->addWidget(openBtn); + + QPushButton *newBtn = new QPushButton("📄 新建工程"); + newBtn->setMinimumHeight(35); + newBtn->setStyleSheet("QPushButton { background: #2E7D32; color: white; font-weight: bold; border: none; border-radius: 4px; padding: 6px 16px; }"); + connect(newBtn, &QPushButton::clicked, this, &MainWindow::newProject); + actionLayout->addWidget(newBtn); + + saveButton = new QPushButton("💾 保存工程 (Ctrl+S)"); + saveButton->setMinimumHeight(35); + saveButton->setStyleSheet("QPushButton { background: #E65100; color: white; font-weight: bold; border: none; border-radius: 4px; padding: 6px 16px; }"); + connect(saveButton, &QPushButton::clicked, this, &MainWindow::saveProject); + actionLayout->addWidget(saveButton); + + generateButton = new QPushButton("🔨 生成 CMake (Ctrl+G)"); + generateButton->setMinimumHeight(35); + generateButton->setStyleSheet("QPushButton { background: #6A1B9A; color: white; font-weight: bold; border: none; border-radius: 4px; padding: 6px 16px; }"); + connect(generateButton, &QPushButton::clicked, this, &MainWindow::generateCMake); + actionLayout->addWidget(generateButton); + + QPushButton *debugBtn = new QPushButton("🐛 编译 Debug"); + debugBtn->setMinimumHeight(35); + debugBtn->setStyleSheet("QPushButton { background: #00838F; color: white; font-weight: bold; border: none; border-radius: 4px; padding: 6px 16px; }"); + connect(debugBtn, &QPushButton::clicked, this, &MainWindow::compileDebug); + actionLayout->addWidget(debugBtn); + this->debugBtn = debugBtn; + + QPushButton *releaseBtn = new QPushButton("🚀 编译 Release"); + releaseBtn->setMinimumHeight(35); + releaseBtn->setStyleSheet("QPushButton { background: #AD1457; color: white; font-weight: bold; border: none; border-radius: 4px; padding: 6px 16px; }"); + connect(releaseBtn, &QPushButton::clicked, this, &MainWindow::compileRelease); + actionLayout->addWidget(releaseBtn); + this->releaseBtn = releaseBtn; + + actionLayout->addStretch(); + + // 创建中心部件 + QWidget *central = new QWidget(this); + setCentralWidget(central); + + QVBoxLayout *mainLayout = new QVBoxLayout(central); + mainLayout->addWidget(actionGroup); + + // 工程基本信息 + QGroupBox *basicGroup = new QGroupBox("📁 工程基本信息"); + QHBoxLayout *basicLayout = new QHBoxLayout(basicGroup); + + basicLayout->addWidget(new QLabel("📁 工程名:")); + projectNameEdit = new QLineEdit(); + projectNameEdit->setPlaceholderText("输入工程名称"); + basicLayout->addWidget(projectNameEdit); + + basicLayout->addStretch(); + basicLayout->addWidget(new QLabel("📂 工程路径:同 VSC 工程文件所在目录")); + + mainLayout->addWidget(basicGroup); + + // 编译工具链 + QGroupBox *toolchainGroup = new QGroupBox("🔧 编译工具链"); + QHBoxLayout *toolchainLayout = new QHBoxLayout(toolchainGroup); + + toolchainLayout->addWidget(new QLabel("编译器:")); + compilerPathEdit = new QLineEdit(); + compilerPathEdit->setPlaceholderText("/usr/bin/gcc"); + toolchainLayout->addWidget(compilerPathEdit); + + QPushButton *compilerBtn = new QPushButton("浏览"); + connect(compilerBtn, &QPushButton::clicked, this, &MainWindow::browseCompilerPath); + toolchainLayout->addWidget(compilerBtn); + + toolchainLayout->addWidget(new QLabel("汇编器:")); + assemblerPathEdit = new QLineEdit(); + assemblerPathEdit->setPlaceholderText("/usr/bin/gcc"); + toolchainLayout->addWidget(assemblerPathEdit); + + QPushButton *assemblerBtn = new QPushButton("浏览"); + connect(assemblerBtn, &QPushButton::clicked, this, &MainWindow::browseAssemblerPath); + toolchainLayout->addWidget(assemblerBtn); + + toolchainLayout->addWidget(new QLabel("链接器:")); + linkerPathEdit = new QLineEdit(); + linkerPathEdit->setPlaceholderText("/usr/bin/gcc"); + toolchainLayout->addWidget(linkerPathEdit); + + QPushButton *linkerBtn = new QPushButton("浏览"); + connect(linkerBtn, &QPushButton::clicked, this, &MainWindow::browseLinkerPath); + toolchainLayout->addWidget(linkerBtn); + + mainLayout->addWidget(toolchainGroup); + + // 主分割器 - 左右布局 + QSplitter *mainSplitter = new QSplitter(Qt::Horizontal); + + // 左侧 - 文件管理 + QWidget *leftPanel = new QWidget(); + QVBoxLayout *leftLayout = new QVBoxLayout(leftPanel); + leftLayout->setContentsMargins(0, 0, 0, 0); + + // 源文件(虚拟目录) + QGroupBox *sourceGroup = new QGroupBox("📄 源文件 (.c) - 虚拟目录"); + QVBoxLayout *sourceLayout = new QVBoxLayout(sourceGroup); + + QHBoxLayout *sourceBtnLayout = new QHBoxLayout(); + QPushButton *addDirBtn = new QPushButton("➕ 目录"); + connect(addDirBtn, &QPushButton::clicked, this, &MainWindow::addVirtualDir); + sourceBtnLayout->addWidget(addDirBtn); + + QPushButton *addFileBtn = new QPushButton("➕ 文件"); + connect(addFileBtn, &QPushButton::clicked, this, &MainWindow::addSourceFile); + sourceBtnLayout->addWidget(addFileBtn); + + QPushButton *removeFileBtn = new QPushButton("➖ 移除"); + connect(removeFileBtn, &QPushButton::clicked, this, &MainWindow::removeSourceFile); + sourceBtnLayout->addWidget(removeFileBtn); + sourceBtnLayout->addStretch(); + + sourceLayout->addLayout(sourceBtnLayout); + + sourceTree = new QTreeWidget(); + sourceTree->setHeaderLabels(QStringList() << "📁 源文件 (虚拟目录)"); + sourceTree->setSelectionMode(QAbstractItemView::ExtendedSelection); + sourceLayout->addWidget(sourceTree); + + leftLayout->addWidget(sourceGroup); + + // 包含目录 + QGroupBox *includeGroup = new QGroupBox("📚 包含目录 (.h)"); + QVBoxLayout *includeLayout = new QVBoxLayout(includeGroup); + + QHBoxLayout *includeBtnLayout = new QHBoxLayout(); + QPushButton *addIncludeBtn = new QPushButton("➕ 添加"); + connect(addIncludeBtn, &QPushButton::clicked, this, &MainWindow::addIncludeDir); + includeBtnLayout->addWidget(addIncludeBtn); + + QPushButton *removeIncludeBtn = new QPushButton("➖ 移除"); + connect(removeIncludeBtn, &QPushButton::clicked, this, &MainWindow::removeIncludeDir); + includeBtnLayout->addWidget(removeIncludeBtn); + includeBtnLayout->addStretch(); + + includeLayout->addLayout(includeBtnLayout); + + includeDirList = new QListWidget(); + includeLayout->addWidget(includeDirList); + + leftLayout->addWidget(includeGroup); + + mainSplitter->addWidget(leftPanel); + + // 右侧 - 编译配置 + QWidget *rightPanel = new QWidget(); + QVBoxLayout *rightLayout = new QVBoxLayout(rightPanel); + rightLayout->setContentsMargins(0, 0, 0, 0); + + // 库文件 + QGroupBox *libGroup = new QGroupBox("📦 库文件 (.a/.so/.lib)"); + QVBoxLayout *libLayout = new QVBoxLayout(libGroup); + + QHBoxLayout *libBtnLayout = new QHBoxLayout(); + QPushButton *addLibBtn = new QPushButton("➕ 添加"); + connect(addLibBtn, &QPushButton::clicked, this, &MainWindow::addLibrary); + libBtnLayout->addWidget(addLibBtn); + + QPushButton *removeLibBtn = new QPushButton("➖ 移除"); + connect(removeLibBtn, &QPushButton::clicked, this, &MainWindow::removeLibrary); + libBtnLayout->addWidget(removeLibBtn); + libBtnLayout->addStretch(); + + libLayout->addLayout(libBtnLayout); + + libraryList = new QListWidget(); + libLayout->addWidget(libraryList); + + rightLayout->addWidget(libGroup); + + // 编译宏 + QGroupBox *defineGroup = new QGroupBox("🏷️ 编译宏定义"); + QVBoxLayout *defineLayout = new QVBoxLayout(defineGroup); + + QHBoxLayout *defineBtnLayout = new QHBoxLayout(); + QPushButton *addDefineBtn = new QPushButton("➕ 添加"); + connect(addDefineBtn, &QPushButton::clicked, this, &MainWindow::addDefine); + defineBtnLayout->addWidget(addDefineBtn); + + QPushButton *removeDefineBtn = new QPushButton("➖ 移除"); + connect(removeDefineBtn, &QPushButton::clicked, this, &MainWindow::removeDefine); + defineBtnLayout->addWidget(removeDefineBtn); + defineBtnLayout->addStretch(); + + defineLayout->addLayout(defineBtnLayout); + + defineList = new QListWidget(); + defineLayout->addWidget(defineList); + + rightLayout->addWidget(defineGroup); + + // 编译选项 + QGroupBox *optionGroup = new QGroupBox("⚙️ 自定义编译选项"); + QVBoxLayout *optionLayout = new QVBoxLayout(optionGroup); + + QHBoxLayout *optionBtnLayout = new QHBoxLayout(); + QPushButton *addOptionBtn = new QPushButton("➕ 添加"); + connect(addOptionBtn, &QPushButton::clicked, this, &MainWindow::addCompilerOption); + optionBtnLayout->addWidget(addOptionBtn); + + QPushButton *removeOptionBtn = new QPushButton("➖ 移除"); + connect(removeOptionBtn, &QPushButton::clicked, this, &MainWindow::removeCompilerOption); + optionBtnLayout->addWidget(removeOptionBtn); + optionBtnLayout->addStretch(); + + optionLayout->addLayout(optionBtnLayout); + + optionList = new QListWidget(); + optionLayout->addWidget(optionList); + + rightLayout->addWidget(optionGroup); + + mainSplitter->addWidget(rightPanel); + mainSplitter->setStretchFactor(0, 1); + mainSplitter->setStretchFactor(1, 1); + + mainLayout->addWidget(mainSplitter); + + // 状态栏 + statusBar()->showMessage("就绪"); +} + +void MainWindow::setupConnections() +{ + connect(projectNameEdit, &QLineEdit::textChanged, this, &MainWindow::onProjectNameChanged); + connect(compilerPathEdit, &QLineEdit::textChanged, this, &MainWindow::onCompilerPathChanged); + connect(assemblerPathEdit, &QLineEdit::textChanged, this, &MainWindow::onAssemblerPathChanged); + connect(linkerPathEdit, &QLineEdit::textChanged, this, &MainWindow::onLinkerPathChanged); + + connect(config, &ProjectConfig::configChanged, [this]() { + modified = true; + updateWindowTitle(); + }); + + connect(cmakeGenerator, &CMakeGenerator::cmakeGenerated, [this](const QString &path) { + if (showCMakeSuccessMsg) { + statusBar()->showMessage("CMake 生成成功:" + path, 3000); + QMessageBox::information(this, "✅ CMake 生成成功", + "CMakeLists.txt 已生成!\n\n" + "📁 位置:" + path + "\n\n" + "🔨 编译方式:\n" + " cd build\n" + " make debug # 编译 Debug 版本\n" + " make release # 编译 Release 版本"); + } + showCMakeSuccessMsg = true; // 重置为 true,下次生成时还会弹窗 + }); + + connect(cmakeGenerator, &CMakeGenerator::errorOccurred, [this](const QString &error) { + statusBar()->showMessage("错误:" + error, 5000); + QMessageBox::critical(this, "错误", error); + }); +} + +void MainWindow::updateWindowTitle() +{ + QString title = "VoiletCStudio - " + config->getProjectName(); + if (modified) { + title += " [*]"; + } + if (!currentFilePath.isEmpty()) { + title += " - " + currentFilePath; + } + setWindowTitle(title); + setWindowModified(modified); +} + +bool MainWindow::maybeSave() +{ + if (!modified) { + return true; + } + + QMessageBox::StandardButton ret = QMessageBox::warning(this, "VoiletCStudio", + "工程已修改,是否保存?", + QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel); + + if (ret == QMessageBox::Save) { + return saveProject(); + } else if (ret == QMessageBox::Cancel) { + return false; + } + return true; +} + +void MainWindow::newProject() +{ + if (!maybeSave()) { + return; + } + + config = new ProjectConfig(this); + currentFilePath.clear(); + modified = false; + + projectNameEdit->clear(); + compilerPathEdit->clear(); + assemblerPathEdit->clear(); + linkerPathEdit->clear(); + sourceTree->clear(); + includeDirList->clear(); + libraryList->clear(); + defineList->clear(); + optionList->clear(); + + updateWindowTitle(); + statusBar()->showMessage("已创建新工程", 3000); +} + +void MainWindow::openProject() +{ + if (!maybeSave()) { + return; + } + + QString filePath = QFileDialog::getOpenFileName(this, "打开工程", "", + "VSC 工程文件 (*.VSC);;所有文件 (*)"); + + if (filePath.isEmpty()) { + return; + } + + if (config->loadConfig(filePath)) { + currentFilePath = filePath; + modified = false; + + projectNameEdit->setText(config->getProjectName()); + compilerPathEdit->setText(config->getCompilerPath()); + assemblerPathEdit->setText(config->getAssemblerPath()); + linkerPathEdit->setText(config->getLinkerPath()); + + sourceTree->clear(); + QMap dirs = config->getVirtualDirs(); + for (auto it = dirs.begin(); it != dirs.end(); ++it) { + QTreeWidgetItem *dirItem = new QTreeWidgetItem(sourceTree); + dirItem->setText(0, it.key()); + for (const QString &file : it->files) { + QTreeWidgetItem *fileItem = new QTreeWidgetItem(dirItem); + fileItem->setText(0, file); + } + } + + includeDirList->clear(); + includeDirList->addItems(config->getIncludeDirs()); + + libraryList->clear(); + libraryList->addItems(config->getLibraries()); + + defineList->clear(); + defineList->addItems(config->getDefines()); + + optionList->clear(); + optionList->addItems(config->getCompilerOptions()); + + updateWindowTitle(); + statusBar()->showMessage("工程已加载:" + filePath, 3000); + } else { + QMessageBox::critical(this, "错误", "无法加载工程文件:" + filePath); + } +} + +void MainWindow::openProjectFile(const QString &filePath) +{ + // 直接打开指定文件(用于命令行和双击打开),跳过 maybeSave + if (!QFile::exists(filePath)) { + QMessageBox::critical(this, "错误", "文件不存在:" + filePath); + return; + } + + if (config->loadConfig(filePath)) { + currentFilePath = filePath; + modified = false; + + projectNameEdit->setText(config->getProjectName()); + compilerPathEdit->setText(config->getCompilerPath()); + assemblerPathEdit->setText(config->getAssemblerPath()); + linkerPathEdit->setText(config->getLinkerPath()); + + sourceTree->clear(); + QMap dirs = config->getVirtualDirs(); + for (auto it = dirs.begin(); it != dirs.end(); ++it) { + QTreeWidgetItem *dirItem = new QTreeWidgetItem(sourceTree); + dirItem->setText(0, it.key()); + for (const QString &file : it->files) { + QTreeWidgetItem *fileItem = new QTreeWidgetItem(dirItem); + fileItem->setText(0, file); + } + } + sourceTree->expandAll(); + + includeDirList->clear(); + includeDirList->addItems(config->getIncludeDirs()); + + libraryList->clear(); + libraryList->addItems(config->getLibraries()); + + defineList->clear(); + defineList->addItems(config->getDefines()); + + optionList->clear(); + optionList->addItems(config->getCompilerOptions()); + + updateWindowTitle(); + statusBar()->showMessage("✅ 工程已加载:" + filePath, 5000); + } else { + QMessageBox::critical(this, "错误", "无法加载工程文件:" + filePath); + } +} + +bool MainWindow::saveProject() +{ + if (currentFilePath.isEmpty()) { + return saveProjectAs(); + } + + config->setProjectName(projectNameEdit->text()); + config->setCompilerPath(compilerPathEdit->text()); + config->setAssemblerPath(assemblerPathEdit->text()); + config->setLinkerPath(linkerPathEdit->text()); + + if (config->saveConfig(currentFilePath)) { + modified = false; + updateWindowTitle(); + statusBar()->showMessage("工程已保存:" + currentFilePath, 3000); + return true; + } else { + QMessageBox::critical(this, "错误", "无法保存工程文件"); + return false; + } +} + +bool MainWindow::saveProjectAs() +{ + QString filePath = QFileDialog::getSaveFileName(this, "保存工程", "", + "VSC 工程文件 (*.VSC);;所有文件 (*)"); + + if (filePath.isEmpty()) { + return false; + } + + currentFilePath = filePath; + return saveProject(); +} + +void MainWindow::generateCMake() +{ + // 先保存工程 + if (!saveProject()) { + return; + } + + // 获取工程目录(VSC 工程文件所在目录) + QString projectDir = QFileInfo(currentFilePath).absolutePath(); + QString buildDir = projectDir + "/build"; + QString cmakePath = projectDir + "/CMakeLists.txt"; + + // 删除旧的 build 目录 + QDir buildPath(buildDir); + if (buildPath.exists()) { + buildPath.removeRecursively(); + statusBar()->showMessage("已删除旧 build 目录", 2000); + } + + // 重新创建 build 目录 + buildPath.mkpath(buildDir); + + // 生成 CMake 文件 + if (cmakeGenerator->generate(config, cmakePath)) { + statusBar()->showMessage("CMake 已生成,正在编译...", 2000); + + // 在 build 目录执行 cmake + QProcess process; + process.setWorkingDirectory(buildDir); + + // 跨平台:Windows 必须指定 MinGW Makefiles 生成器 + QStringList cmakeArgs; +#ifdef Q_OS_WIN32 + cmakeArgs << "-G" << "MinGW Makefiles" << ".."; +#else + cmakeArgs << ".."; +#endif + process.start("cmake", cmakeArgs); + process.waitForFinished(10000); + + QString output = process.readAllStandardOutput(); + QString error = process.readAllStandardError(); + + if (process.exitCode() == 0) { + statusBar()->showMessage("CMake 配置成功!", 3000); + // 不弹窗了,编译按钮会直接打开编译窗口 + } else { + statusBar()->showMessage("CMake 配置失败", 3000); + QMessageBox::warning(this, "警告", + "CMake 配置完成但有警告:\n" + error); + } + } +} +void MainWindow::onProjectNameChanged(const QString &text) +{ + config->setProjectName(text); +} + +void MainWindow::onCompilerPathChanged() +{ + config->setCompilerPath(compilerPathEdit->text()); +} + +void MainWindow::onAssemblerPathChanged() +{ + config->setAssemblerPath(assemblerPathEdit->text()); +} + +void MainWindow::onLinkerPathChanged() +{ + config->setLinkerPath(linkerPathEdit->text()); +} + +void MainWindow::addVirtualDir() +{ + bool ok; + QString dirName = QInputDialog::getText(this, "添加虚拟目录", + "目录名称:", QLineEdit::Normal, "", &ok); + + if (ok && !dirName.isEmpty()) { + config->addVirtualDir(dirName); + + QTreeWidgetItem *item = new QTreeWidgetItem(sourceTree); + item->setText(0, dirName); + sourceTree->expandAll(); + } +} + +void MainWindow::addSourceFile() +{ + QTreeWidgetItem *currentItem = sourceTree->currentItem(); + if (!currentItem) { + QMessageBox::warning(this, "警告", "请先选择虚拟目录"); + return; + } + + QString dirName = currentItem->text(0); + + // 使用 JSON 文件所在目录作为初始目录 + QString initialDir = currentFilePath.isEmpty() ? "" : QFileInfo(currentFilePath).absolutePath(); + + QStringList files = QFileDialog::getOpenFileNames(this, "选择源文件", initialDir, + "C 源文件 (*.c);;所有文件 (*)"); + + for (const QString &file : files) { + config->addSourceFile(dirName, file); + + QTreeWidgetItem *fileItem = new QTreeWidgetItem(currentItem); + fileItem->setText(0, file); + } +} + +void MainWindow::removeSourceFile() +{ + QTreeWidgetItem *currentItem = sourceTree->currentItem(); + if (!currentItem) { + return; + } + + QString dirName = currentItem->parent() ? currentItem->parent()->text(0) : currentItem->text(0); + QString fileName = currentItem->text(0); + + if (currentItem->parent()) { + config->removeSourceFile(dirName, fileName); + delete currentItem; + } else { + config->clearVirtualDirs(); + delete currentItem; + } +} + +void MainWindow::addIncludeDir() +{ + // 使用 JSON 文件所在目录作为初始目录 + QString initialDir = currentFilePath.isEmpty() ? "" : QFileInfo(currentFilePath).absolutePath(); + + QString dir = QFileDialog::getExistingDirectory(this, "选择包含目录", initialDir); + if (!dir.isEmpty()) { + config->addIncludeDir(dir); + includeDirList->addItem(dir); + } +} + +void MainWindow::removeIncludeDir() +{ + int row = includeDirList->currentRow(); + if (row >= 0) { + QString dir = includeDirList->takeItem(row)->text(); + config->removeIncludeDir(dir); + } +} + +void MainWindow::addLibrary() +{ + // 使用 JSON 文件所在目录作为初始目录 + QString initialDir = currentFilePath.isEmpty() ? "" : QFileInfo(currentFilePath).absolutePath(); + + QStringList files = QFileDialog::getOpenFileNames(this, "选择库文件", initialDir, + "库文件 (*.a *.so *.lib *.dll);;所有文件 (*)"); + + for (const QString &file : files) { + config->addLibrary(file); + libraryList->addItem(file); + } +} + +void MainWindow::removeLibrary() +{ + int row = libraryList->currentRow(); + if (row >= 0) { + QString lib = libraryList->takeItem(row)->text(); + config->removeLibrary(lib); + } +} + +void MainWindow::addDefine() +{ + bool ok; + QString macro = QInputDialog::getText(this, "添加编译宏", + "宏定义 (例如 DEBUG):", QLineEdit::Normal, "", &ok); + + if (ok && !macro.isEmpty()) { + config->addDefine(macro); + defineList->addItem(macro); + } +} + +void MainWindow::removeDefine() +{ + int row = defineList->currentRow(); + if (row >= 0) { + QString macro = defineList->takeItem(row)->text(); + config->removeDefine(macro); + } +} + +void MainWindow::addCompilerOption() +{ + bool ok; + QString option = QInputDialog::getText(this, "添加编译选项", + "编译选项 (例如 -Wall):", QLineEdit::Normal, "", &ok); + + if (ok && !option.isEmpty()) { + config->addCompilerOption(option); + optionList->addItem(option); + } +} + +void MainWindow::removeCompilerOption() +{ + int row = optionList->currentRow(); + if (row >= 0) { + QString option = optionList->takeItem(row)->text(); + config->removeCompilerOption(option); + } +} + +void MainWindow::browseCompilerPath() +{ + QString path = QFileDialog::getOpenFileName(this, "选择编译器", "", + "可执行文件 (*)"); + if (!path.isEmpty()) { + compilerPathEdit->setText(path); + } +} + +void MainWindow::browseAssemblerPath() +{ + QString path = QFileDialog::getOpenFileName(this, "选择汇编器", "", + "可执行文件 (*)"); + if (!path.isEmpty()) { + assemblerPathEdit->setText(path); + } +} + +void MainWindow::browseLinkerPath() +{ + QString path = QFileDialog::getOpenFileName(this, "选择链接器", "", + "可执行文件 (*)"); + if (!path.isEmpty()) { + linkerPathEdit->setText(path); + } +} + +void MainWindow::compileDebug() +{ + showCMakeSuccessMsg = false; // 编译时不弹 CMake 成功窗 + compileProject("Debug"); +} + +void MainWindow::compileRelease() +{ + showCMakeSuccessMsg = false; // 编译时不弹 CMake 成功窗 + compileProject("Release"); +} + +void MainWindow::compileProject(const QString &buildType) +{ + // 先校验 + if (currentFilePath.isEmpty()) { + QMessageBox::warning(this, "警告", "请先保存工程!"); + return; + } + + QString projectDir = QFileInfo(currentFilePath).absolutePath(); + + // ★ 第一时间弹窗,让用户知道开始工作了 + QDialog *compileDialog = new QDialog(this); + compileDialog->setWindowTitle("编译 " + buildType); + compileDialog->resize(800, 600); + compileDialog->setAttribute(Qt::WA_DeleteOnClose); + + QVBoxLayout *dialogLayout = new QVBoxLayout(compileDialog); + + QLabel *titleLabel = new QLabel("🔨 正在准备编译 " + buildType + "..."); + titleLabel->setStyleSheet("font-size: 18px; font-weight: bold; color: #333;"); + dialogLayout->addWidget(titleLabel); + + QTextEdit *outputEdit = new QTextEdit(); + outputEdit->setReadOnly(true); + outputEdit->setFont(QFont("Consolas", 10)); + outputEdit->setStyleSheet("background: #1e1e1e; color: #d4d4d4;"); + dialogLayout->addWidget(outputEdit); + + QPushButton *closeBtn = new QPushButton("❌ 关闭"); + closeBtn->setMinimumHeight(35); + closeBtn->setStyleSheet("QPushButton { background: #C62828; color: white; font-weight: bold; font-size: 14px; border: none; border-radius: 4px; padding: 6px 16px; }"); + closeBtn->setEnabled(false); // 编译未完成时不可关闭 + dialogLayout->addWidget(closeBtn); + + // ★ 锁死编译按钮 + debugBtn->setEnabled(false); + debugBtn->setText("🔒 编译中..."); + releaseBtn->setEnabled(false); + releaseBtn->setText("🔒 编译中..."); + + // 弹窗关闭时解锁按钮 + connect(compileDialog, &QDialog::finished, this, [this]() { + debugBtn->setEnabled(true); + debugBtn->setText("🐛 编译 Debug"); + releaseBtn->setEnabled(true); + releaseBtn->setText("🚀 编译 Release"); + }); + + compileDialog->show(); + QApplication::processEvents(); // 立即渲染弹窗 + + // ===== 阶段 1:保存工程 ===== + outputEdit->append("💾 正在保存工程配置..."); + if (!saveProject()) { + outputEdit->append("❌ 保存失败!"); + titleLabel->setText("❌ 保存失败"); + titleLabel->setStyleSheet("font-size: 18px; font-weight: bold; color: #f44336;"); + closeBtn->setEnabled(true); + return; + } + outputEdit->append("✅ 工程已保存\n"); + QApplication::processEvents(); + + // ===== 阶段 2:清理旧 build ===== + QString buildDir = projectDir + "/build"; + outputEdit->append("🧹 正在清理旧的构建目录..."); + QDir buildPath(buildDir); + if (buildPath.exists()) { + buildPath.removeRecursively(); + outputEdit->append("✅ 已删除旧 build 目录\n"); + } else { + outputEdit->append("✅ build 目录不存在,跳过清理\n"); + } + buildPath.mkpath(buildDir); + QApplication::processEvents(); + + // ===== 阶段 3:生成 CMakeLists.txt ===== + outputEdit->append("📄 正在生成 CMakeLists.txt..."); + QString cmakePath = projectDir + "/CMakeLists.txt"; + if (!cmakeGenerator->generate(config, cmakePath)) { + outputEdit->append("❌ 生成 CMake 失败!"); + titleLabel->setText("❌ CMake 生成失败"); + titleLabel->setStyleSheet("font-size: 18px; font-weight: bold; color: #f44336;"); + closeBtn->setEnabled(true); + return; + } + outputEdit->append("✅ CMakeLists.txt 已生成\n"); + QApplication::processEvents(); + + // ===== 阶段 4:CMake 配置 ===== + outputEdit->append("📋 正在配置 CMake..."); + QProcess cmakeProcess; + cmakeProcess.setWorkingDirectory(buildDir); + + QStringList cmakeArgs; +#ifdef Q_OS_WIN32 + cmakeArgs << "-G" << "MinGW Makefiles" << "-DCMAKE_BUILD_TYPE=" + buildType << ".."; +#else + cmakeArgs << "-DCMAKE_BUILD_TYPE=" + buildType << ".."; +#endif + cmakeProcess.start("cmake", cmakeArgs); + cmakeProcess.waitForFinished(30000); + + QString cmakeOutput = cmakeProcess.readAllStandardOutput(); + if (!cmakeOutput.isEmpty()) { + outputEdit->append(cmakeOutput); + } + QString cmakeError = cmakeProcess.readAllStandardError(); + if (!cmakeError.isEmpty()) { + outputEdit->append("⚠️ CMake 警告/错误:\n" + cmakeError); + } + + if (cmakeProcess.exitCode() != 0) { + outputEdit->append("\n❌ CMake 配置失败!错误代码:" + QString::number(cmakeProcess.exitCode())); + titleLabel->setText("❌ CMake 配置失败"); + titleLabel->setStyleSheet("font-size: 18px; font-weight: bold; color: #f44336;"); + closeBtn->setEnabled(true); + return; + } + outputEdit->append("✅ CMake 配置完成\n"); + QApplication::processEvents(); + + // ===== 阶段 5:编译 ===== + outputEdit->append("🔨 正在编译 " + buildType + "...\n"); + titleLabel->setText("🔨 编译 " + buildType + " 中..."); + QApplication::processEvents(); + + QProcess makeProcess; + makeProcess.setWorkingDirectory(buildDir); + +#ifdef Q_OS_WIN32 + QString makeCmd = "mingw32-make"; +#else + QString makeCmd = "make"; +#endif + makeProcess.start(makeCmd, QStringList() << buildType.toLower()); + + // 实时输出 + connect(&makeProcess, &QProcess::readyReadStandardOutput, [&outputEdit, &makeProcess]() { + outputEdit->append(makeProcess.readAllStandardOutput()); + outputEdit->verticalScrollBar()->setValue(outputEdit->verticalScrollBar()->maximum()); + }); + + connect(&makeProcess, &QProcess::readyReadStandardError, [&outputEdit, &makeProcess]() { + outputEdit->append(makeProcess.readAllStandardError()); + outputEdit->verticalScrollBar()->setValue(outputEdit->verticalScrollBar()->maximum()); + }); + + makeProcess.waitForFinished(300000); // 5 分钟超时 + + if (makeProcess.exitCode() == 0) { + outputEdit->append("\n✅ 编译成功!"); + titleLabel->setText("✅ 编译 " + buildType + " 成功!"); + titleLabel->setStyleSheet("font-size: 18px; font-weight: bold; color: #4CAF50;"); + } else { + outputEdit->append("\n❌ 编译失败!错误代码:" + QString::number(makeProcess.exitCode())); + titleLabel->setText("❌ 编译 " + buildType + " 失败!"); + titleLabel->setStyleSheet("font-size: 18px; font-weight: bold; color: #f44336;"); + } + + // ★ 编译完成,允许关闭和重新编译 + closeBtn->setEnabled(true); + connect(closeBtn, &QPushButton::clicked, compileDialog, &QDialog::accept); +} + diff --git a/src/mainwindow.h b/src/mainwindow.h new file mode 100644 index 0000000..016c4c4 --- /dev/null +++ b/src/mainwindow.h @@ -0,0 +1,105 @@ +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include "projectconfig.h" +#include "cmakegenerator.h" + +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + MainWindow(QWidget *parent = nullptr); + ~MainWindow(); + + // 通过命令行/双击打开工程文件 + void openProjectFile(const QString &filePath); + +private slots: + // 文件操作 + void newProject(); + void openProject(); + bool saveProject(); + bool saveProjectAs(); + + // 生成 CMake + void generateCMake(); + + // 编译 + void compileDebug(); + void compileRelease(); + void compileProject(const QString &buildType); + + // UI 更新 + void onProjectNameChanged(const QString &text); + void onCompilerPathChanged(); + void onAssemblerPathChanged(); + void onLinkerPathChanged(); + + // 虚拟目录操作 + void addVirtualDir(); + void addSourceFile(); + void removeSourceFile(); + + // 包含目录操作 + void addIncludeDir(); + void removeIncludeDir(); + + // 库文件操作 + void addLibrary(); + void removeLibrary(); + + // 编译宏操作 + void addDefine(); + void removeDefine(); + + // 编译选项操作 + void addCompilerOption(); + void removeCompilerOption(); + + // 浏览文件 + void browseCompilerPath(); + void browseAssemblerPath(); + void browseLinkerPath(); + +private: + void setupUI(); + void setupConnections(); + void updateWindowTitle(); + bool maybeSave(); + + // 组件 + QLineEdit *projectNameEdit; + QLineEdit *compilerPathEdit; + QLineEdit *assemblerPathEdit; + QLineEdit *linkerPathEdit; + + QTreeWidget *sourceTree; // 虚拟目录树 + QListWidget *includeDirList; // 包含目录列表 + QListWidget *libraryList; // 库文件列表 + QListWidget *defineList; // 编译宏列表 + QListWidget *optionList; // 编译选项列表 + + QPushButton *saveButton; + QPushButton *generateButton; + QPushButton *debugBtn; + QPushButton *releaseBtn; + + // 配置和生成器 + ProjectConfig *config; + CMakeGenerator *cmakeGenerator; + + QString currentFilePath; + bool modified; + bool showCMakeSuccessMsg; // 控制是否显示 CMake 成功弹窗 +}; + +#endif // MAINWINDOW_H diff --git a/src/projectconfig.cpp b/src/projectconfig.cpp new file mode 100644 index 0000000..29010a0 --- /dev/null +++ b/src/projectconfig.cpp @@ -0,0 +1,351 @@ +#include "projectconfig.h" +#include +#include +#include +#include +#include + +ProjectConfig::ProjectConfig(QObject *parent) + : QObject(parent) +{ + m_projectName = "Untitled"; + m_projectPath = ""; + m_compilerPath = "gcc"; + m_assemblerPath = "gcc"; + m_linkerPath = "gcc"; + m_outputDir = "./build"; +} + +bool ProjectConfig::loadConfig(const QString &filePath) +{ + QFile file(filePath); + if (!file.open(QIODevice::ReadOnly)) { + return false; + } + + QJsonDocument doc = QJsonDocument::fromJson(file.readAll()); + file.close(); + + if (doc.isNull()) { + return false; + } + + fromJson(doc.object()); + return true; +} + +bool ProjectConfig::saveConfig(const QString &filePath) +{ + QFile file(filePath); + if (!file.open(QIODevice::WriteOnly)) { + return false; + } + + QJsonDocument doc(toJson()); + file.write(doc.toJson()); + file.close(); + + return true; +} + +QJsonObject ProjectConfig::toJson() const +{ + QJsonObject json; + + // 基本信息 + json["projectName"] = m_projectName; + json["projectPath"] = m_projectPath; + json["outputDir"] = m_outputDir; + + // 工具链 + json["compilerPath"] = m_compilerPath; + json["assemblerPath"] = m_assemblerPath; + json["linkerPath"] = m_linkerPath; + + // 虚拟目录 + QJsonObject virtualDirs; + for (auto it = m_virtualDirs.begin(); it != m_virtualDirs.end(); ++it) { + QJsonObject dirObj; + dirObj["name"] = it->name; + QJsonArray files; + for (const QString &f : it->files) { + files.append(f); + } + dirObj["files"] = files; + virtualDirs[it.key()] = dirObj; + } + json["virtualDirs"] = virtualDirs; + + // 包含目录 + QJsonArray includeDirs; + for (const QString &dir : m_includeDirs) { + includeDirs.append(dir); + } + json["includeDirs"] = includeDirs; + + // 库文件 + QJsonArray libraries; + for (const QString &lib : m_libraries) { + libraries.append(lib); + } + json["libraries"] = libraries; + + // 编译宏 + QJsonArray defines; + for (const QString &def : m_defines) { + defines.append(def); + } + json["defines"] = defines; + + // 编译选项 + QJsonArray options; + for (const QString &opt : m_compilerOptions) { + options.append(opt); + } + json["compilerOptions"] = options; + + return json; +} + +void ProjectConfig::fromJson(const QJsonObject &json) +{ + m_projectName = json["projectName"].toString(); + m_projectPath = json["projectPath"].toString(); + m_outputDir = json["outputDir"].toString(); + m_compilerPath = json["compilerPath"].toString(); + m_assemblerPath = json["assemblerPath"].toString(); + m_linkerPath = json["linkerPath"].toString(); + + // 虚拟目录 + m_virtualDirs.clear(); + QJsonObject virtualDirs = json["virtualDirs"].toObject(); + for (auto it = virtualDirs.begin(); it != virtualDirs.end(); ++it) { + VirtualDir dir; + dir.name = it->toObject()["name"].toString(); + QJsonArray files = it->toObject()["files"].toArray(); + for (const QJsonValue &f : files) { + dir.files.append(f.toString()); + } + m_virtualDirs[it.key()] = dir; + } + + // 包含目录 + m_includeDirs.clear(); + QJsonArray includeDirs = json["includeDirs"].toArray(); + for (const QJsonValue &dir : includeDirs) { + m_includeDirs.append(dir.toString()); + } + + // 库文件 + m_libraries.clear(); + QJsonArray libraries = json["libraries"].toArray(); + for (const QJsonValue &lib : libraries) { + m_libraries.append(lib.toString()); + } + + // 编译宏 + m_defines.clear(); + QJsonArray defines = json["defines"].toArray(); + for (const QJsonValue &def : defines) { + m_defines.append(def.toString()); + } + + // 编译选项 + m_compilerOptions.clear(); + QJsonArray options = json["compilerOptions"].toArray(); + for (const QJsonValue &opt : options) { + m_compilerOptions.append(opt.toString()); + } + + emit configChanged(); +} + +// Getter/Setter 实现 +void ProjectConfig::setProjectName(const QString &name) { + if (m_projectName != name) { + m_projectName = name; + emit configChanged(); + } +} + +QString ProjectConfig::getProjectName() const { + return m_projectName; +} + +void ProjectConfig::setProjectPath(const QString &path) { + if (m_projectPath != path) { + m_projectPath = path; + emit configChanged(); + } +} + +QString ProjectConfig::getProjectPath() const { + return m_projectPath; +} + +void ProjectConfig::setCompilerPath(const QString &path) { + if (m_compilerPath != path) { + m_compilerPath = path; + emit configChanged(); + } +} + +QString ProjectConfig::getCompilerPath() const { + return m_compilerPath; +} + +void ProjectConfig::setAssemblerPath(const QString &path) { + if (m_assemblerPath != path) { + m_assemblerPath = path; + emit configChanged(); + } +} + +QString ProjectConfig::getAssemblerPath() const { + return m_assemblerPath; +} + +void ProjectConfig::setLinkerPath(const QString &path) { + if (m_linkerPath != path) { + m_linkerPath = path; + emit configChanged(); + } +} + +QString ProjectConfig::getLinkerPath() const { + return m_linkerPath; +} + +void ProjectConfig::setOutputDir(const QString &dir) { + if (m_outputDir != dir) { + m_outputDir = dir; + emit configChanged(); + } +} + +QString ProjectConfig::getOutputDir() const { + return m_outputDir; +} + +// 虚拟目录操作 +void ProjectConfig::addVirtualDir(const QString &dirName) { + if (!m_virtualDirs.contains(dirName)) { + VirtualDir dir; + dir.name = dirName; + m_virtualDirs[dirName] = dir; + emit configChanged(); + } +} + +void ProjectConfig::addSourceFile(const QString &dirName, const QString &filePath) { + if (m_virtualDirs.contains(dirName)) { + if (!m_virtualDirs[dirName].files.contains(filePath)) { + m_virtualDirs[dirName].files.append(filePath); + emit configChanged(); + } + } +} + +void ProjectConfig::removeSourceFile(const QString &dirName, const QString &filePath) { + if (m_virtualDirs.contains(dirName)) { + m_virtualDirs[dirName].files.removeAll(filePath); + emit configChanged(); + } +} + +QMap ProjectConfig::getVirtualDirs() const { + return m_virtualDirs; +} + +void ProjectConfig::clearVirtualDirs() { + m_virtualDirs.clear(); + emit configChanged(); +} + +// 包含目录操作 +void ProjectConfig::addIncludeDir(const QString &dir) { + if (!m_includeDirs.contains(dir)) { + m_includeDirs.append(dir); + emit configChanged(); + } +} + +void ProjectConfig::removeIncludeDir(const QString &dir) { + m_includeDirs.removeAll(dir); + emit configChanged(); +} + +QStringList ProjectConfig::getIncludeDirs() const { + return m_includeDirs; +} + +void ProjectConfig::clearIncludeDirs() { + m_includeDirs.clear(); + emit configChanged(); +} + +// 库文件操作 +void ProjectConfig::addLibrary(const QString &libPath) { + if (!m_libraries.contains(libPath)) { + m_libraries.append(libPath); + emit configChanged(); + } +} + +void ProjectConfig::removeLibrary(const QString &libPath) { + m_libraries.removeAll(libPath); + emit configChanged(); +} + +QStringList ProjectConfig::getLibraries() const { + return m_libraries; +} + +void ProjectConfig::clearLibraries() { + m_libraries.clear(); + emit configChanged(); +} + +// 编译宏操作 +void ProjectConfig::addDefine(const QString ¯o) { + if (!m_defines.contains(macro)) { + m_defines.append(macro); + emit configChanged(); + } +} + +void ProjectConfig::removeDefine(const QString ¯o) { + m_defines.removeAll(macro); + emit configChanged(); +} + +QStringList ProjectConfig::getDefines() const { + return m_defines; +} + +void ProjectConfig::clearDefines() { + m_defines.clear(); + emit configChanged(); +} + +// 编译选项操作 +void ProjectConfig::addCompilerOption(const QString &option) { + if (!m_compilerOptions.contains(option)) { + m_compilerOptions.append(option); + emit configChanged(); + } +} + +void ProjectConfig::removeCompilerOption(const QString &option) { + m_compilerOptions.removeAll(option); + emit configChanged(); +} + +QStringList ProjectConfig::getCompilerOptions() const { + return m_compilerOptions; +} + +void ProjectConfig::clearCompilerOptions() { + m_compilerOptions.clear(); + emit configChanged(); +} diff --git a/src/projectconfig.h b/src/projectconfig.h new file mode 100644 index 0000000..19cb42f --- /dev/null +++ b/src/projectconfig.h @@ -0,0 +1,101 @@ +#ifndef PROJECTCONFIG_H +#define PROJECTCONFIG_H + +#include +#include +#include +#include +#include + +// 虚拟目录结构 +struct VirtualDir { + QString name; + QStringList files; // .c 文件列表 +}; + +class ProjectConfig : public QObject +{ + Q_OBJECT + +public: + explicit ProjectConfig(QObject *parent = nullptr); + + // 加载/保存配置 + bool loadConfig(const QString &filePath); + bool saveConfig(const QString &filePath); + + // 工程基本信息 + void setProjectName(const QString &name); + QString getProjectName() const; + + void setProjectPath(const QString &path); + QString getProjectPath() const; + + // 编译工具链 + void setCompilerPath(const QString &path); + QString getCompilerPath() const; + + void setAssemblerPath(const QString &path); + QString getAssemblerPath() const; + + void setLinkerPath(const QString &path); + QString getLinkerPath() const; + + // 输出目录 + void setOutputDir(const QString &dir); + QString getOutputDir() const; + + // 虚拟目录(.c 文件) + void addVirtualDir(const QString &dirName); + void addSourceFile(const QString &dirName, const QString &filePath); + void removeSourceFile(const QString &dirName, const QString &filePath); + QMap getVirtualDirs() const; + void clearVirtualDirs(); + + // 包含目录(.h 文件) + void addIncludeDir(const QString &dir); + void removeIncludeDir(const QString &dir); + QStringList getIncludeDirs() const; + void clearIncludeDirs(); + + // 库文件 + void addLibrary(const QString &libPath); + void removeLibrary(const QString &libPath); + QStringList getLibraries() const; + void clearLibraries(); + + // 编译宏 + void addDefine(const QString ¯o); + void removeDefine(const QString ¯o); + QStringList getDefines() const; + void clearDefines(); + + // 自定义编译选项 + void addCompilerOption(const QString &option); + void removeCompilerOption(const QString &option); + QStringList getCompilerOptions() const; + void clearCompilerOptions(); + + // 转换为 JSON + QJsonObject toJson() const; + void fromJson(const QJsonObject &json); + +signals: + void configChanged(); + +private: + QString m_projectName; + QString m_projectPath; + QString m_compilerPath; + QString m_assemblerPath; + QString m_linkerPath; + QString m_outputDir; + + QMap m_virtualDirs; // 虚拟目录 + QStringList m_includeDirs; // 包含目录 + QStringList m_libraries; // 库文件 + QStringList m_defines; // 编译宏 + QStringList m_compilerOptions; // 自定义编译选项 +}; + +#endif // PROJECTCONFIG_H diff --git a/需求规格说明书.md b/需求规格说明书.md new file mode 100644 index 0000000..20d2bfb --- /dev/null +++ b/需求规格说明书.md @@ -0,0 +1,542 @@ +# VoiletCStudio 需求规格说明书 + +**版本号:** v1.1 +**项目名称:** 紫罗兰 C 工程配置器(VoiletCStudio) +**开发日期:** 2026-04-09 +**最后更新:** 2026-04-28 +**作者:** 虾哥 + +--- + +## 📋 目录 + +1. [项目概述](#1-项目概述) +2. [功能需求](#2-功能需求) +3. [界面设计](#3-界面设计) +4. [技术规格](#4-技术规格) +5. [使用说明](#5-使用说明) +6. [文件结构](#6-文件结构) +7. [配置文件格式](#7-配置文件格式) +8. [编译流程](#8-编译流程) + +--- + +## 1. 项目概述 + +### 1.1 项目背景 + +VoiletCStudio 是一款跨平台的 C 工程配置工具,旨在简化 C 语言项目的构建流程。通过图形化界面管理工程配置,自动生成 CMakeLists.txt 文件,支持一键编译 Debug 和 Release 版本。 + +### 1.2 目标用户 + +- C 语言开发者 +- 嵌入式系统开发人员 +- 需要跨平台编译的项目团队 +- 从 IDE 迁移到 CMake 的开发者 + +### 1.3 核心价值 + +- **简化配置**:图形化界面替代手动编写 CMakeLists.txt +- **跨平台支持**:Windows (MinGW) / Linux (GCC) / macOS (Clang) +- **提高效率**:一键生成和编译,减少重复劳动 +- **易于上手**:类似 MDK 的虚拟目录管理,降低学习成本 + +--- + +## 2. 功能需求 + +### 2.1 工程文件管理 + +| 功能 ID | 功能名称 | 功能描述 | 优先级 | +|--------|---------|---------|--------| +| F-001 | 新建工程 | 创建新的 C 工程配置 | 高 | +| F-002 | 打开工程 | 加载已保存的 JSON 配置文件 | 高 | +| F-003 | 保存工程 | 保存当前配置到 JSON 文件 | 高 | +| F-004 | 另存为 | 将工程保存到指定位置 | 中 | +| F-005 | 拖拽打开 | 拖拽 JSON 文件到窗口打开工程 | 中 | + +### 2.2 编译工具链配置 + +| 功能 ID | 功能名称 | 功能描述 | 优先级 | +|--------|---------|---------|--------| +| F-010 | 编译器配置 | 设置 C 编译器路径(gcc/clang 等) | 高 | +| F-011 | 汇编器配置 | 设置汇编器路径 | 中 | +| F-012 | 链接器配置 | 设置链接器路径 | 中 | + +### 2.3 文件管理 + +| 功能 ID | 功能名称 | 功能描述 | 优先级 | +|--------|---------|---------|--------| +| F-020 | 虚拟目录 | 创建类似 MDK 的虚拟文件夹 | 高 | +| F-021 | 源文件管理 | 添加/移除 .c 源文件 | 高 | +| F-022 | 包含目录 | 添加/移除 .h 头文件目录 | 高 | +| F-023 | 库文件管理 | 添加/移除 .a/.so/.lib 库文件 | 高 | + +### 2.4 编译配置 + +| 功能 ID | 功能名称 | 功能描述 | 优先级 | +|--------|---------|---------|--------| +| F-030 | 编译宏 | 添加/移除预编译宏定义 | 高 | +| F-031 | 编译选项 | 添加/移除自定义编译参数 | 中 | + +### 2.5 CMake 生成 + +| 功能 ID | 功能名称 | 功能描述 | 优先级 | +|--------|---------|---------|--------| +| F-040 | 生成 CMake | 自动生成 CMakeLists.txt 文件 | 高 | +| F-041 | 命令提示 | 显示编译命令使用说明 | 高 | + +### 2.6 编译功能 + +| 功能 ID | 功能名称 | 功能描述 | 优先级 | +|--------|---------|---------|--------| +| F-050 | 编译 Debug | 编译 Debug 版本(带调试信息) | 高 | +| F-051 | 编译 Release | 编译 Release 版本(优化发布) | 高 | +| F-052 | 实时输出 | 显示编译过程实时日志 | 高 | +| F-053 | 自动清理 | 编译前自动清空 build 目录 | 高 | + +--- + +## 3. 界面设计 + +### 3.1 主界面布局 + +``` +┌─────────────────────────────────────────────────────────────┐ +│ VoiletCStudio - C 工程配置器 │ +├─────────────────────────────────────────────────────────────┤ +│ ⚡ 快捷操作 │ +│ [📂 打开] [📄 新建] [💾 保存] [🔨 生成 CMake] [🐛 编译 Debug] [🚀 编译 Release] │ +├─────────────────────────────────────────────────────────────┤ +│ 📁 工程基本信息 │ +│ 工程名:[____________] │ +├─────────────────────────────────────────────────────────────┤ +│ 🔧 编译工具链 │ +│ 编译器:[____] 汇编器:[____] 链接器:[____] │ +├─────────────────────────────────────────────────────────────┤ +│ 📁 源文件 │ 📦 库文件 │ +│ ├─ 📁 App │ ├─ libmylib.a │ +│ │ ├─ main.c │ 🏷️ 编译宏 │ +│ │ └─ utils.c │ ├─ DEBUG │ +│ 📚 包含目录 │ ⚙️ 编译选项 │ +│ ├─ ./include │ ├─ -Wall │ +├─────────────────────────────────────────────────────────────┤ +│ 就绪 [状态栏] │ +└─────────────────────────────────────────────────────────────┘ +``` + +### 3.2 按钮说明 + +| 按钮 | 颜色 | 功能 | 快捷键 | +|------|------|------|--------| +| 📂 打开工程 | 🔵 蓝色 | 打开已有工程 | Ctrl+O | +| 📄 新建工程 | 🟢 绿色 | 创建新工程 | Ctrl+N | +| 💾 保存工程 | 🟠 橙色 | 保存配置 | Ctrl+S | +| 🔨 生成 CMake | 🟣 紫色 | 生成 CMakeLists.txt | Ctrl+G | +| 🐛 编译 Debug | 🔵 青色 | 编译 Debug 版本 | - | +| 🚀 编译 Release | 🌸 粉色 | 编译 Release 版本 | - | + +--- + +## 4. 技术规格 + +### 4.1 开发环境 + +| 项目 | 规格 | +|------|------| +| 开发语言 | C++ 11 | +| GUI 框架 | Qt 5 | +| 构建系统 | qmake | +| 目标平台 | Windows / Linux / macOS | + +### 4.2 系统要求 + +| 项目 | 最低要求 | 推荐配置 | +|------|---------|---------| +| 操作系统 | Windows 7 / Ubuntu 18.04 / macOS 10.14 | Windows 10 / Ubuntu 20.04 / macOS 11+ | +| 内存 | 512 MB | 2 GB | +| 磁盘空间 | 100 MB | 500 MB | +| 编译器 | GCC 5.0+ / MinGW / Clang | GCC 9.0+ / MinGW-w64 / Clang 10+ | + +### 4.3 依赖库 + +| 库名称 | 版本 | 用途 | +|--------|------|------| +| Qt Core | 5.12+ | 核心功能 | +| Qt GUI | 5.12+ | 图形界面 | +| Qt Widgets | 5.12+ | 窗口组件 | +| CMake | 3.10+ | 构建系统 | +| GCC/MinGW | 5.0+ | C 编译器 | + +--- + +## 5. 使用说明 + +### 5.1 快速开始 + +#### 步骤 1:新建工程 + +1. 点击【📄 新建工程】按钮 +2. 输入工程名称 +3. 点击【💾 保存工程】,选择保存位置 + +#### 步骤 2:配置工具链 + +1. 点击编译器旁的【浏览】按钮 +2. 选择编译器路径(如 `/usr/bin/gcc`) +3. 同样配置汇编器和链接器(通常与编译器相同) + +#### 步骤 3:添加源文件 + +1. 点击【➕ 目录】创建虚拟文件夹(如 "App") +2. 选中目录,点击【➕ 文件】添加 .c 文件 +3. 重复添加所有源文件 + +#### 步骤 4:配置包含目录 + +1. 切换到右侧"包含目录"面板 +2. 点击【➕ 添加】选择头文件目录 +3. 添加所有需要的包含路径 + +#### 步骤 5:添加库文件(可选) + +1. 切换到"库文件"面板 +2. 点击【➕ 添加】选择 .a/.so/.lib 文件 +3. 添加所有依赖库 + +#### 步骤 6:配置编译选项 + +1. 切换到"编译宏"面板 +2. 点击【➕ 添加】输入宏定义(如 `DEBUG`) +3. 切换到"编译选项"面板 +4. 添加自定义编译参数(如 `-Wall`) + +#### 步骤 7:生成 CMake + +1. 点击【🔨 生成 CMake】按钮 +2. 查看弹出的编译命令提示 +3. CMakeLists.txt 已生成到工程目录 + +#### 步骤 8:编译工程 + +**方式一:使用内置编译** +1. 点击【🐛 编译 Debug】或【🚀 编译 Release】 +2. 查看编译输出窗口 +3. 编译完成后查看结果 + +**方式二:手动编译** +```bash +cd build +make debug # 编译 Debug 版本 +make release # 编译 Release 版本 +``` + +--- + +## 6. 文件结构 + +### 6.1 工程目录结构 + +``` +MyProject/ +├── config.json # 工程配置文件 +├── CMakeLists.txt # CMake 配置文件(自动生成) +├── build/ # 编译输出目录(自动生成) +│ ├── Makefile # Makefile(自动生成) +│ ├── debug/ # Debug 编译产物 +│ │ └── MyProject # Debug 可执行文件 +│ └── release/ # Release 编译产物 +│ └── MyProject # Release 可执行文件 +├── src/ # 源文件目录(用户创建) +│ ├── main.c +│ └── utils.c +└── include/ # 头文件目录(用户创建) + └── utils.h +``` + +### 6.2 源代码结构 + +``` +VoiletCStudio/ +├── src/ +│ ├── main.cpp # 程序入口 +│ ├── mainwindow.cpp/h # 主窗口实现 +│ ├── projectconfig.cpp/h # 配置管理 +│ └── cmakegenerator.cpp/h # CMake 生成 +├── VoiletCStudio.pro # Qt 项目文件 +├── README.md # 使用说明 +└── 需求规格说明书.md # 本文档 +``` + +--- + +## 7. 配置文件格式 + +### 7.1 JSON 配置示例 + +```json +{ + "projectName": "MyProject", + "projectPath": "/home/anonymous/Desktop/MyProject", + "compilerPath": "/usr/bin/gcc", + "assemblerPath": "/usr/bin/gcc", + "linkerPath": "/usr/bin/gcc", + "outputDir": "./build", + "virtualDirs": { + "App": { + "name": "App", + "files": [ + "/home/anonymous/Desktop/MyProject/src/main.c", + "/home/anonymous/Desktop/MyProject/src/utils.c" + ] + }, + "Drivers": { + "name": "Drivers", + "files": [ + "/home/anonymous/Desktop/MyProject/src/drivers/driver.c" + ] + } + }, + "includeDirs": [ + "/home/anonymous/Desktop/MyProject/include" + ], + "libraries": [ + "/usr/lib/libmylib.a" + ], + "defines": [ + "DEBUG", + "VERSION=1.0" + ], + "compilerOptions": [ + "-Wall", + "-Wextra" + ] +} +``` + +### 7.2 字段说明 + +| 字段名 | 类型 | 说明 | 必填 | +|--------|------|------|------| +| projectName | string | 工程名称 | 是 | +| projectPath | string | 工程路径 | 是 | +| compilerPath | string | 编译器路径 | 是 | +| assemblerPath | string | 汇编器路径 | 否 | +| linkerPath | string | 链接器路径 | 否 | +| outputDir | string | 输出目录 | 否 | +| virtualDirs | object | 虚拟目录配置 | 是 | +| includeDirs | array | 包含目录列表 | 否 | +| libraries | array | 库文件列表 | 否 | +| defines | array | 编译宏列表 | 否 | +| compilerOptions | array | 编译选项列表 | 否 | + +--- + +## 8. 编译流程 + +### 8.1 CMake 生成流程 + +``` +用户点击【生成 CMake】 + ↓ +读取工程配置(JSON) + ↓ +生成 CMakeLists.txt + ↓ +显示编译命令提示 + ↓ +完成 +``` + +### 8.2 编译执行流程 + +``` +用户点击【编译 Debug/Release】 + ↓ +保存工程配置 + ↓ +删除旧的 build 目录 + ↓ +创建新的 build 目录 + ↓ +生成 CMakeLists.txt + ↓ +执行 cmake -DCMAKE_BUILD_TYPE=Debug/Release .. + ↓ +执行 make debug/release + ↓ +实时显示编译输出 + ↓ +显示编译结果 + ↓ +完成 +``` + +### 8.3 CMakeLists.txt 结构 + +```cmake +# ======================================== +# CMakeLists.txt - VoiletCStudio 自动生成 +# 工程名称:MyProject +# 生成时间:2026-04-09 10:00:00 +# 跨平台支持:Windows (MinGW) / Linux (GCC) / macOS (Clang) +# ======================================== + +cmake_minimum_required(VERSION 3.10) + +# 项目名称 +project(MyProject C) + +# C 标准 +set(CMAKE_C_STANDARD 11) +set(CMAKE_C_STANDARD_REQUIRED ON) + +# 输出目录配置 +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/./build) +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/./build) +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/./build) + +# 编译器配置 +set(CMAKE_C_COMPILER "/usr/bin/gcc") + +# ======================================== +# 源文件配置(虚拟目录结构) +# ======================================== + +# 分组:App +set(SRCS_App + /home/anonymous/Desktop/MyProject/src/main.c + /home/anonymous/Desktop/MyProject/src/utils.c +) + +# 分组:Drivers +set(SRCS_Drivers + /home/anonymous/Desktop/MyProject/src/drivers/driver.c +) + +# 合并所有源文件 +set(SOURCES + /home/anonymous/Desktop/MyProject/src/main.c + /home/anonymous/Desktop/MyProject/src/utils.c + /home/anonymous/Desktop/MyProject/src/drivers/driver.c +) + +# 创建可执行文件 +add_executable(MyProject ${SOURCES}) + +# 包含目录 +target_include_directories(MyProject PRIVATE + /home/anonymous/Desktop/MyProject/include +) + +# 链接库 +target_link_libraries(MyProject PRIVATE + /usr/lib/libmylib.a +) + +# 预编译宏定义 +target_compile_definitions(MyProject PRIVATE + DEBUG + VERSION=1.0 +) + +# 自定义编译选项 +target_compile_options(MyProject PRIVATE + -Wall + -Wextra +) + +# ======================================== +# 构建类型配置 +# ======================================== + +# 默认构建类型:Debug +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE Debug CACHE STRING "Build type" FORCE) +endif() + +# Debug 模式配置(自动添加 DEBUG 宏) +set(CMAKE_C_FLAGS_DEBUG "-g -O0 -DDEBUG" CACHE STRING "Debug flags" FORCE) + +# Release 模式配置 +set(CMAKE_C_FLAGS_RELEASE "-O2 -DNDEBUG" CACHE STRING "Release flags" FORCE) + +# ======================================== +# 自定义 Make 目标:make debug / make release +# ======================================== + +# make debug - 编译 Debug 版本(自动添加 DEBUG 宏) +add_custom_target(debug + COMMAND ${CMAKE_COMMAND} -DCMAKE_BUILD_TYPE=Debug ${CMAKE_SOURCE_DIR} + COMMAND ${CMAKE_COMMAND} --build ${CMAKE_BINARY_DIR} --config Debug + COMMENT "Building Debug version" + VERBATIM +) + +# make release - 编译 Release 版本 +add_custom_target(release + COMMAND ${CMAKE_COMMAND} -DCMAKE_BUILD_TYPE=Release ${CMAKE_SOURCE_DIR} + COMMAND ${CMAKE_COMMAND} --build ${CMAKE_BINARY_DIR} --config Release + COMMENT "Building Release version" + VERBATIM +) + +# 使用说明: +# cmake -B build # 配置(默认 Debug) +# cmake --build build # 编译(使用当前 CMAKE_BUILD_TYPE) +# make debug # 编译 Debug 版本(自动添加 DEBUG 宏) +# make release # 编译 Release 版本 +``` + +--- + +## 9. 常见问题 + +### 9.1 编译失败 + +**问题:** 点击编译后提示找不到编译器 + +**解决:** +1. 检查编译器路径是否正确 +2. 确认编译器已安装(`gcc --version`) +3. Windows 用户确认 MinGW 已正确安装 + +### 9.2 CMake 生成失败 + +**问题:** 点击生成 CMake 无反应 + +**解决:** +1. 检查工程是否已保存 +2. 确认保存路径有写入权限 +3. 查看状态栏错误提示 + +### 9.3 拖拽无反应 + +**问题:** 拖拽 JSON 文件到窗口没反应 + +**解决:** +1. 确认文件扩展名为 `.json` +2. 确认文件是有效的工程配置 +3. 尝试使用"打开工程"按钮 + +--- + +## 10. 版本历史 + +| 版本 | 日期 | 更新内容 | +|------|------|---------| +| v1.0 | 2026-04-09 | 初始版本,完整功能发布 | +| v1.1 | 2026-04-28 | 修复 Windows 跨平台兼容性:CMAKE_C_COMPILER 设置提前到 project() 之前;自动添加 MinGW Makefiles 生成器;mingw32-make 替代 make;按钮高对比度配色优化 | + +--- + +## 11. 联系方式 + +- **作者:** 虾哥 +- **邮箱:** anonymous@linuxacme.com +- **项目地址:** https://git.linuxacme.com/iorebuild/VoiletCStudio.git + +--- + +**文档版本:** v1.0 +**最后更新:** 2026-04-09