Qt 기반으로 개발한 프로그램을 손쉽게 설치할 수 있도록, 디렉터리 구조 생성 및 압축 과정을 자동화하는 방법에 대해 알아보겠습니다. 이 글에서는 Qt Creator를 사용하여 특정 디렉터리 구조를 생성하고, 선택한 폴더를 압축한 후 지정된 위치에 저장하는 코드를 구현하는 방법을 설명합니다.
참고: 코드에는 특정 사용자 정보나 프로그램 이름 대신 일반적인 용어로 수정되었습니다.
관련 링크
- Qt Creator를 활용한 자동 업데이트 설정: 구조와 계획
- Installer: 버전별 관리와 설치 프로그램 생성 가이드
- 가위바위보 게임 Qt Creator로 만들기
- GitHub Pages와 Qt Installer Framework를 이용해 온라인 설치 프로그램 만들기
- Qt로 다운로드 매니저 구현하기: 진행 상태 및 자동 설치 기능
1. mainwindow.ui를 먼저 구현해야됩니다.
UI 구성 요소
- Program Name (프로그램 이름) 입력란
- 위치: 메인 창 상단
- 이름: programNameInput
- 설명: 사용자가 설치하려는 프로그램의 이름을 입력하는 텍스트 입력란입니다.
- 기능: 입력된 프로그램 이름을 기반으로 설치 경로가 자동 업데이트됩니다.
- Version (버전) 입력란
- 위치: Program Name 입력란 아래
- 이름: versionInput
- 설명: 프로그램 버전을 입력하는 텍스트 입력란입니다.
- 기능: 입력된 버전을 기반으로 설치 경로가 자동 업데이트됩니다.
- Install Path (설치 경로) 표시란
- 위치: Version 입력란 아래
- 이름: installPathInput
- 설명: 자동 생성된 설치 경로를 표시하는 비활성 텍스트 입력란입니다.
- 기능: 사용자가 입력한 프로그램 이름과 버전 정보를 기반으로 설치 경로를 실시간으로 업데이트하여 표시합니다.
- Select Folder (폴더 선택) 버튼
- 위치: 설치 경로 표시란 아래
- 이름: selectFileButton
- 설명: 사용자가 압축할 폴더를 선택할 수 있는 버튼입니다.
- 기능: 버튼을 클릭하면 파일 선택 대화 상자가 열리고, 사용자는 압축할 폴더를 선택할 수 있습니다.
- Save (저장) 버튼
- 위치: 폴더 선택 버튼 아래
- 이름: saveButton
- 설명: 디렉터리 구조를 생성하고 XML 파일을 생성하며 선택한 폴더를 압축하여 저장하는 버튼입니다.
- 기능: 버튼을 클릭하면 compressSelectedFile()와 saveVersionToXml() 함수가 호출되어 전체 설치 파일 생성 과정이 실행됩니다.
- Result (결과) 레이블
- 위치: 메인 창 하단
- 이름: resultLabel
- 설명: 디렉터리 및 파일 생성, 압축 결과를 출력하는 레이블입니다.
- 기능: 실행 후 성공 또는 실패 메시지를 표시합니다.
- File Path (선택된 파일 경로) 레이블
- 위치: Select Folder 버튼 옆 또는 아래
- 이름: filePathLabel
- 설명: 사용자가 선택한 폴더의 경로를 표시하는 레이블입니다.
- 기능: Select Folder 버튼을 통해 폴더를 선택하면 선택된 폴더의 경로를 표시합니다.
2. InstallerFileManager 클래스 개요
이 코드는 InstallerFileManager 클래스를 사용하여 디렉터리 구조를 만들고 XML 파일 및 라이선스 파일을 생성합니다. 또한, 선택한 릴리스 폴더를 ZIP 파일로 압축하여 저장할 수 있습니다.
#include <QFile>
#include <QDir>
#include <QMessageBox>
#include <QDate>
#include <QProcess>
#include <QDebug>
#include <QCoreApplication>
이 클래스는 programName, version, installPath 세 가지 변수를 기반으로 생성됩니다. 각각의 변수는 프로그램 이름, 버전, 그리고 설치 경로를 지정합니다.
디렉터리 구조 생성 함수
createDirectoryStructure() 함수는 설치 파일들이 저장될 폴더 구조를 자동으로 생성합니다.
* 프로젝트 구성예시 및 관련 글 : https://cbjh-4.tistory.com/260
bool InstallerFileManager::createDirectoryStructure() {
QDir dir;
QStringList paths = {
installPath + "/config",
installPath + "/packages",
installPath + "/packages/com.generic." + programName.toLower().replace(" ", "") + "/meta",
installPath + "/packages/com.generic." + programName.toLower().replace(" ", "") + "/data",
installPath + "/repository"
};
for (const QString& path : paths) {
if (!dir.mkpath(path)) {
QMessageBox::warning(nullptr, "Error", QString("Failed to create directory: %1").arg(path));
return false;
}
// 각 디렉터리에 __init__.py 파일 생성
QFile initFile(path + "/__init__.py");
if (!initFile.open(QIODevice::WriteOnly)) {
QMessageBox::warning(nullptr, "Error", QString("Failed to create __init__.py in: %1").arg(path));
return false;
}
initFile.close();
}
return true;
}
paths 리스트에는 필요한 디렉터리 구조가 포함되어 있으며, 각각의 디렉터리에 __init__.py 파일이 생성됩니다.
config.xml 및 package.xml 생성 함수
설치에 필요한 config.xml과 package.xml을 자동으로 생성하는 함수들입니다.
createConfigXml() 함수는 config.xml을 생성하며, programName과 version에 맞는 내용을 자동으로 작성합니다.
bool InstallerFileManager::createConfigXml() {
// XML 작성 시작
QDomDocument doc;
QDomElement root = doc.createElement("Installer");
doc.appendChild(root);
// 기본 정보 추가
root.appendChild(createElement(doc, "Name", programName));
root.appendChild(createElement(doc, "Version", version));
root.appendChild(createElement(doc, "Title", programName + " Installer"));
root.appendChild(createElement(doc, "Publisher", "GenericCompany"));
// Repository URL 설정
QDomElement repositories = doc.createElement("RemoteRepositories");
QDomElement repository = doc.createElement("Repository");
repository.appendChild(createElement(doc, "Url", QString("https://example.com/%1-installer/v%2/repository")
.arg(programName.toLower()).arg(version)));
root.appendChild(repositories);
return saveXmlToFile(doc, installPath + "/config/config.xml");
}
3. 압축 함수: 선택한 폴더를 ZIP으로 압축하기
압축 과정은 PowerShell의 Compress-Archive 명령어를 이용해 지정한 폴더를 압축하고 최상위 폴더 없이 하위 파일들만 포함하도록 설정합니다.
bool InstallerFileManager::compressFileToZip(const QString& folderPath) {
QString exeDir = QCoreApplication::applicationDirPath();
QString tempArchivePath = exeDir + "/" + QFileInfo(folderPath).fileName() + ".zip";
QFile::remove(tempArchivePath); // 기존 압축 파일 삭제
QProcess process;
QStringList arguments;
QStringList filePaths;
QDir dir(folderPath);
dir.setFilter(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot);
QFileInfoList files = dir.entryInfoList();
for (const QFileInfo& fileInfo : files) {
filePaths << fileInfo.absoluteFilePath();
}
// PowerShell 명령어 생성
arguments << "-Command"
<< QString("Compress-Archive -Path '%1' -DestinationPath '%2' -Force")
.arg(filePaths.join("','"))
.arg(QDir::toNativeSeparators(tempArchivePath));
process.start("powershell", arguments);
process.waitForFinished();
QString errorOutput = process.readAllStandardError();
if (process.exitCode() != 0) {
QMessageBox::warning(nullptr, "Compression Error", "Failed to compress folder:\n" + errorOutput);
return false;
}
return true;
}
4. MainWindow 클래스: UI와 기능 연결
MainWindow 클래스는 사용자가 선택한 프로그램 이름, 버전, 설치 경로를 기반으로 디렉터리 구조를 생성하고, 릴리스 폴더를 선택하여 압축을 수행합니다.
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent), ui(new Ui::MainWindow)
{
ui->setupUi(this);
connect(ui->saveButton, &QPushButton::clicked, this, &MainWindow::handleSaveButtonClick);
connect(ui->selectFileButton, &QPushButton::clicked, this, &MainWindow::selectFile);
connect(ui->programNameInput, &QLineEdit::textChanged, this, &MainWindow::updateInstallPath);
connect(ui->versionInput, &QLineEdit::textChanged, this, &MainWindow::updateInstallPath);
updateInstallPath();
}
5. 사용 예시: 폴더 선택 후 압축하기
사용자는 UI에서 프로그램 이름, 버전, 설치 경로를 입력하고, Save 버튼을 클릭하여 디렉터리 구조를 생성할 수 있습니다. Select Folder 버튼을 통해 압축할 폴더를 선택한 후, 선택된 폴더의 하위 파일들을 ZIP으로 압축하여 지정된 위치에 저장합니다.
void MainWindow::compressSelectedFile() {
if (selectedFilePath.isEmpty()) {
QMessageBox::warning(this, "Error", "No folder selected for compression.");
return;
}
if (fileManager && fileManager->compressFileToZip(selectedFilePath)) {
ui->resultLabel->setText("Folder compressed successfully!");
} else {
ui->resultLabel->setText("Folder compression failed.");
}
}
6. 전체 코드
InstallerFileManager 클래스
InstallerFileManager 클래스는 설치 디렉터리 구조를 생성하고, config.xml 및 package.xml 파일을 작성하며, 라이선스 파일을 생성하고 선택한 폴더를 ZIP 압축으로 저장하는 기능을 포함합니다.
#include "installerfilemanager.h"
#include
#include
#include
#include
#include
#include
#include
// 클래스 생성자
InstallerFileManager::InstallerFileManager(const QString& programName, const QString& version, const QString& installPath)
: programName(programName), version(version), installPath(installPath)
{
}
// 디렉터리 구조 생성 함수
bool InstallerFileManager::createDirectoryStructure() {
QDir dir;
QStringList paths = {
installPath + "/config",
installPath + "/packages",
installPath + "/packages/com.generic." + programName.toLower().replace(" ", "") + "/meta",
installPath + "/packages/com.generic." + programName.toLower().replace(" ", "") + "/data",
installPath + "/repository"
};
for (const QString& path : paths) {
if (!dir.mkpath(path)) {
QMessageBox::warning(nullptr, "Error", QString("Failed to create directory: %1").arg(path));
return false;
}
// 각 디렉터리에 __init__.py 파일 생성
QFile initFile(path + "/__init__.py");
if (!initFile.open(QIODevice::WriteOnly)) {
QMessageBox::warning(nullptr, "Error", QString("Failed to create __init__.py in: %1").arg(path));
return false;
}
initFile.close();
}
return true;
}
// config.xml 생성 함수
bool InstallerFileManager::createConfigXml() {
QDomDocument doc;
QDomElement root = doc.createElement("Installer");
doc.appendChild(root);
root.appendChild(createElement(doc, "Name", programName));
root.appendChild(createElement(doc, "Version", version));
root.appendChild(createElement(doc, "Title", programName + " Installer"));
root.appendChild(createElement(doc, "Publisher", "GenericCompany"));
root.appendChild(createElement(doc, "ProductUrl", "https://example.com/" + programName.toLower() + "-installer/"));
root.appendChild(createElement(doc, "InstallerWindowIcon", "installericon.png"));
root.appendChild(createElement(doc, "InstallerApplicationIcon", "installericon.png"));
root.appendChild(createElement(doc, "Logo", "logo.png"));
root.appendChild(createElement(doc, "TargetDir", QString("@DesktopDir@/%1_v%2").arg(programName).arg(version)));
QDomElement repositories = doc.createElement("RemoteRepositories");
QDomElement repository = doc.createElement("Repository");
repository.appendChild(createElement(doc, "Url", QString("https://example.com/%1-installer/v%2/repository")
.arg(programName.toLower()).arg(version)));
repositories.appendChild(repository);
root.appendChild(repositories);
return saveXmlToFile(doc, installPath + "/config/config.xml");
}
// package.xml 생성 함수
bool InstallerFileManager::createPackageXml() {
QDomDocument doc;
QDomElement root = doc.createElement("Package");
doc.appendChild(root);
root.appendChild(createElement(doc, "DisplayName", programName + " (v" + version + ")"));
root.appendChild(createElement(doc, "Description", "A simple " + programName + " game."));
root.appendChild(createElement(doc, "Version", version));
root.appendChild(createElement(doc, "ReleaseDate", QDate::currentDate().toString("yyyy-MM-dd")));
root.appendChild(createElement(doc, "Name", "com.generic." + programName.toLower().replace(" ", "")));
QDomElement licenses = doc.createElement("Licenses");
QDomElement license = doc.createElement("License");
license.setAttribute("name", "License Agreement");
license.setAttribute("file", "license.txt");
licenses.appendChild(license);
root.appendChild(licenses);
root.appendChild(createElement(doc, "DownloadableArchives", programName + "_v" + version + ".zip"));
return saveXmlToFile(doc, installPath + "/packages/com.generic." + programName.toLower().replace(" ", "") + "/meta/package.xml");
}
// license.txt 생성 함수
bool InstallerFileManager::createLicenseFile() {
QFile file(installPath + "/packages/com.generic." + programName.toLower().replace(" ", "") + "/meta/license.txt");
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
QMessageBox::warning(nullptr, "Error", "Failed to create license.txt");
return false;
}
QTextStream out(&file);
out << "Generic Game License Agreement\n";
out << "------------------------------------------\n\n";
out << "This software was created by GenericCompany.\n\n";
out << "Users are free to use, modify, and distribute this software under certain conditions.\n\n";
file.close();
return true;
}
// XML 요소 생성 함수
QDomElement InstallerFileManager::createElement(QDomDocument& doc, const QString& tag, const QString& value) {
QDomElement element = doc.createElement(tag);
element.appendChild(doc.createTextNode(value));
return element;
}
// XML 파일 저장 함수
bool InstallerFileManager::saveXmlToFile(const QDomDocument& doc, const QString& filePath) {
QFile file(filePath);
if (!file.open(QIODevice::WriteOnly)) {
QMessageBox::warning(nullptr, "Error", QString("Failed to create %1").arg(filePath));
return false;
}
QTextStream stream(&file);
stream << doc.toString();
file.close();
return true;
}
// 폴더를 ZIP으로 압축하는 함수
bool InstallerFileManager::compressFileToZip(const QString& folderPath) {
QString exeDir = QCoreApplication::applicationDirPath();
QString tempArchivePath = exeDir + "/" + QFileInfo(folderPath).fileName() + ".zip";
QFile::remove(tempArchivePath);
QProcess process;
QStringList arguments;
QStringList filePaths;
QDir dir(folderPath);
dir.setFilter(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot);
QFileInfoList files = dir.entryInfoList();
for (const QFileInfo& fileInfo : files) {
filePaths << fileInfo.absoluteFilePath();
}
arguments << "-Command"
<< QString("Compress-Archive -Path '%1' -DestinationPath '%2' -Force")
.arg(filePaths.join("','"))
.arg(QDir::toNativeSeparators(tempArchivePath));
process.start("powershell", arguments);
process.waitForFinished();
QString errorOutput = process.readAllStandardError();
if (process.exitCode() != 0) {
QMessageBox::warning(nullptr, "Compression Error", "Failed to compress folder:\n" + errorOutput);
return false;
}
return true;
}
MainWindow 클래스
MainWindow 클래스는 UI와의 연동을 처리합니다. 사용자는 이 인터페이스에서 프로그램 이름, 버전, 설치 경로를 입력하고, 폴더를 선택하여 압축할 수 있습니다.
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "installerfilemanager.h"
#include <QFileDialog>
#include <QMessageBox>
#include <QProcess>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent), ui(new Ui::MainWindow)
{
ui->setupUi(this);
connect(ui->saveButton, &QPushButton::clicked, this, &MainWindow::handleSaveButtonClick);
connect(ui->selectFileButton, &QPushButton::clicked, this, &MainWindow::selectFile);
connect(ui->programNameInput, &QLineEdit::textChanged, this, &MainWindow::updateInstallPath);
connect(ui->versionInput, &QLineEdit::textChanged, this, &MainWindow::updateInstallPath);
updateInstallPath();
}
MainWindow::~MainWindow() {
delete ui;
}
void MainWindow::handleSaveButtonClick() {
compressSelectedFile();
saveVersionToXml();
}
void MainWindow::updateInstallPath() {
QString programName = ui->programNameInput->text().isEmpty() ? "ProgramName" : ui->programNameInput->text();
QString version = ui->versionInput->text().isEmpty() ? "1.0.0" : ui->versionInput->text();
QString defaultPath = QString("D:/qt/%1/v%2").arg(programName).arg(version);
ui->installPathInput->setText(defaultPath);
}
void MainWindow::saveVersionToXml() {
QString programName = ui->programNameInput->text();
QString version = ui->versionInput->text();
QString installPath = ui->installPathInput->text();
fileManager = new InstallerFileManager(programName, version, installPath);
if (!fileManager->createDirectoryStructure() ||
!fileManager->createConfigXml() ||
!fileManager->createPackageXml() ||
!fileManager->createLicenseFile()) {
ui->resultLabel->setText("Failed to create files or directory structure.");
return;
}
QString tempArchivePath = QCoreApplication::applicationDirPath() + "/" + QFileInfo(selectedFilePath).fileName() + ".zip";
QString finalArchivePath = installPath + "/packages/com.generic." + programName.toLower().replace(" ", "") + "/data/" + QFileInfo(selectedFilePath).fileName() + ".zip";
if (QFile::exists(finalArchivePath)) {
QFile::remove(finalArchivePath);
}
QFile::rename(tempArchivePath, finalArchivePath);
ui->resultLabel->setText("Files and directory structure created and compressed file moved successfully!");
}
void MainWindow::selectFile() {
selectedFilePath = QFileDialog::getExistingDirectory(this, "Select Folder to Compress");
if (!selectedFilePath.isEmpty()) {
ui->filePathLabel->setText(selectedFilePath);
}
}
void MainWindow::compressSelectedFile() {
if (selectedFilePath.isEmpty()) {
QMessageBox::warning(this, "Error", "No folder selected for compression.");
return;
}
if (fileManager && fileManager->compressFileToZip(selectedFilePath)) {
ui->resultLabel->setText("Folder compressed successfully!");
} else {
ui->resultLabel->setText("Folder compression failed.");
}
}
마무리
이 코드를 통해 설치 프로그램에 필요한 폴더 구조와 파일을 자동으로 생성하고, 선택한 폴더를 ZIP으로 압축하여 최종 설치 파일을 생성할 수 있습니다. 이를 통해 배포 파일을 보다 쉽게 준비할 수 있습니다.
'임베디드 관련 카테고리 > Qt' 카테고리의 다른 글
Qt Creator를 활용한 자동 업데이트 설정: 구조와 계획 (3) | 2024.11.07 |
---|---|
Qt로 다운로드 매니저 구현하기: 진행 상태 및 자동 설치 기능 (1) | 2024.10.30 |
Qt Creator를 사용한 GitHub API 기반 버전 관리 모듈 분리 및 사용하기 (0) | 2024.10.29 |
Rock Paper Scissors Installer: 버전별 관리와 설치 프로그램 생성 가이드 (Windows CMD) (1) | 2024.10.28 |
GitHub Pages와 Qt Installer Framework를 이용해 온라인 설치 프로그램 만들기 (1) | 2024.10.25 |
댓글