Skip to content

Commit a7ef31a

Browse files
committed
release: add v0.1.0 packaging
1 parent 531a798 commit a7ef31a

7 files changed

Lines changed: 218 additions & 29 deletions

File tree

.github/workflows/ci.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@ jobs:
7373
run: |
7474
$exe = if ($env:RUNNER_OS -eq "Windows") {
7575
"build/ci/FluentSerialAssistant.exe"
76+
} elseif ($env:RUNNER_OS -eq "macOS") {
77+
"build/ci/FluentSerialAssistant.app/Contents/MacOS/FluentSerialAssistant"
7678
} else {
7779
"build/ci/FluentSerialAssistant"
7880
}

.github/workflows/release.yml

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
name: Release
2+
3+
on:
4+
push:
5+
tags:
6+
- "v*"
7+
8+
permissions:
9+
contents: write
10+
11+
jobs:
12+
package:
13+
name: ${{ matrix.name }}
14+
runs-on: ${{ matrix.os }}
15+
strategy:
16+
fail-fast: false
17+
matrix:
18+
include:
19+
- name: Windows / MSVC
20+
os: windows-latest
21+
asset: windows-x64
22+
- name: Linux / GCC
23+
os: ubuntu-latest
24+
asset: linux-x64
25+
- name: macOS / Clang
26+
os: macos-15
27+
asset: macos
28+
29+
steps:
30+
- name: Checkout
31+
uses: actions/checkout@v4
32+
with:
33+
submodules: recursive
34+
35+
- name: Install Linux packages
36+
if: runner.os == 'Linux'
37+
run: |
38+
sudo apt-get update
39+
sudo apt-get install -y ninja-build libgl1-mesa-dev
40+
41+
- name: Install macOS packages
42+
if: runner.os == 'macOS'
43+
run: brew install ninja
44+
45+
- name: Setup MSVC
46+
if: runner.os == 'Windows'
47+
uses: ilammy/msvc-dev-cmd@v1
48+
49+
- name: Install Qt
50+
uses: jurplel/install-qt-action@v4
51+
with:
52+
version: '6.8.0'
53+
modules: 'qtserialport'
54+
cache: true
55+
56+
- name: Configure
57+
shell: pwsh
58+
run: |
59+
$qtPrefix = $env:QT_ROOT_DIR
60+
if (-not $qtPrefix -and $env:Qt6_DIR) {
61+
$qtPrefix = (Resolve-Path (Join-Path $env:Qt6_DIR "../../..")).Path
62+
}
63+
if (-not $qtPrefix) {
64+
throw "Qt prefix was not found."
65+
}
66+
cmake -S . -B build/release -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH="$qtPrefix"
67+
68+
- name: Build
69+
run: cmake --build build/release --parallel
70+
71+
- name: Package
72+
shell: bash
73+
run: |
74+
set -euo pipefail
75+
mkdir -p artifacts
76+
release_name="FluentSerialAssistant-${GITHUB_REF_NAME}-${{ matrix.asset }}"
77+
package_root="${RUNNER_TEMP}/${release_name}"
78+
rm -rf "$package_root"
79+
mkdir -p "$package_root"
80+
81+
if [ "${{ runner.os }}" = "Windows" ]; then
82+
binary_target="$(find build/release -maxdepth 6 -type f -name 'FluentSerialAssistant.exe' | head -n 1 || true)"
83+
if [ -z "$binary_target" ]; then
84+
echo "Unable to locate Windows executable."
85+
find build/release -maxdepth 6 -print
86+
exit 1
87+
fi
88+
cp "$binary_target" "$package_root/"
89+
windeployqt --release --no-translations --no-system-d3d-compiler --no-opengl-sw "$package_root/FluentSerialAssistant.exe"
90+
elif [ "${{ runner.os }}" = "macOS" ]; then
91+
app_target="$(find build/release -maxdepth 6 -type d -name 'FluentSerialAssistant.app' | head -n 1 || true)"
92+
if [ -z "$app_target" ]; then
93+
echo "Unable to locate macOS app bundle."
94+
find build/release -maxdepth 6 -print
95+
exit 1
96+
fi
97+
cp -R "$app_target" "$package_root/"
98+
macdeployqt "$package_root/FluentSerialAssistant.app"
99+
else
100+
binary_target="$(find build/release -maxdepth 6 -type f -name 'FluentSerialAssistant' | head -n 1 || true)"
101+
if [ -z "$binary_target" ]; then
102+
echo "Unable to locate Linux executable."
103+
find build/release -maxdepth 6 -print
104+
exit 1
105+
fi
106+
cp "$binary_target" "$package_root/"
107+
chmod +x "$package_root/FluentSerialAssistant"
108+
fi
109+
110+
cp README.md CHANGELOG.md LICENSE "$package_root/"
111+
112+
if [ "${{ runner.os }}" = "Windows" ] && command -v 7z >/dev/null 2>&1; then
113+
7z a "artifacts/${release_name}.zip" "$package_root/."
114+
else
115+
tar -czf "artifacts/${release_name}.tar.gz" -C "${RUNNER_TEMP}" "${release_name}"
116+
fi
117+
118+
- name: Publish release assets
119+
uses: softprops/action-gh-release@v2
120+
with:
121+
body_path: docs/release-notes/${{ github.ref_name }}.md
122+
files: artifacts/*
123+
fail_on_unmatched_files: true
124+
overwrite_files: true

CHANGELOG.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
本项目遵循面向用户的变更记录格式。版本号在正式发布前可能继续调整。
44

5-
## [0.1.0] - 未发布
5+
## [0.1.0] - 2026-07-07
66

77
### 新增
88

@@ -23,15 +23,17 @@
2323
- 浅色、深色、跟随系统主题和主题色配置。
2424
- Windows 紧凑发布包脚本。
2525
- GitHub Actions 三平台构建 CI。
26+
- GitHub Releases 三平台发布资产。
2627

2728
### 变更
2829

2930
- UI 聚焦终端和设置两页,隐藏未完成入口。
3031
- 主窗口默认尺寸调整为 `1120x900` 并启动居中。
3132
- 发送按钮改为纯图标按钮。
33+
- 接收设置和发送设置的双列复选框对齐。
3234

3335
### 已知限制
3436

3537
- 暂未提供自动化 GUI 测试。
36-
- 暂未提供 macOS 和 Linux 打包脚本
38+
- Linux 发布包暂未做发行版独立运行时封装
3739
- 暂未开放包列表、快速绘图、宏命令和测试序列。

CMakeLists.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,4 +88,9 @@ endif()
8888

8989
set_target_properties(FluentSerialAssistant PROPERTIES
9090
WIN32_EXECUTABLE TRUE
91+
MACOSX_BUNDLE TRUE
92+
MACOSX_BUNDLE_BUNDLE_NAME "Fluent Serial Assistant"
93+
MACOSX_BUNDLE_GUI_IDENTIFIER "tech.zhangshu.FluentSerialAssistant"
94+
MACOSX_BUNDLE_SHORT_VERSION_STRING "${PROJECT_VERSION}"
95+
MACOSX_BUNDLE_BUNDLE_VERSION "${PROJECT_VERSION}"
9196
)

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ Fluent 串口助手是一个基于 C++17、Qt 6 Widgets 和 FluentQtWidgets 的
4444
- 设置页
4545
- Windows 紧凑发布包脚本
4646
- GitHub Actions 三平台构建 CI
47+
- GitHub Releases 三平台发布资产
4748

4849
暂未开放:
4950

docs/release-notes/v0.1.0.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# Fluent 串口助手 v0.1.0
2+
3+
首个公开预览版本,聚焦串口终端工作台和基础设置能力。
4+
5+
## 主要功能
6+
7+
- Fluent 风格串口终端工作台。
8+
- 串口扫描、连接、断开和自动重连。
9+
- 波特率、数据位、校验位、停止位、流控、RTS、DTR 配置。
10+
- 文本、HEX、混合三种终端显示模式。
11+
- 自动断帧显示和时间戳。
12+
- TX 记录颜色选择。
13+
- 文本和 HEX 数据发送。
14+
- None、CR、LF、CRLF 行结束符。
15+
- 发送历史和循环发送。
16+
- RX/TX 字节计数。
17+
- 暂停显示、自动滚动、清空终端、重置计数。
18+
- TXT、CSV、BIN 会话导出。
19+
- 接收数据保存到文件。
20+
- 浅色、深色、跟随系统主题和主题色配置。
21+
22+
## 发布资产
23+
24+
- Windows x64:包含 `windeployqt` 部署后的运行文件。
25+
- macOS:包含 `.app` 应用包。
26+
- Linux x64:包含可执行文件和项目文档,系统运行库需由发行版提供。
27+
28+
## 已知限制
29+
30+
- 暂未提供自动化 GUI 测试。
31+
- 暂未开放包列表、快速绘图、宏命令和测试序列。

src/app/view/workbench_page.cpp

Lines changed: 51 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include <QtGui/QTextCharFormat>
2121
#include <QtGui/QTextCursor>
2222
#include <QtGui/QTextOption>
23+
#include <QtWidgets/QGridLayout>
2324
#include <QtWidgets/QHBoxLayout>
2425
#include <QtWidgets/QLabel>
2526
#include <QtWidgets/QPlainTextEdit>
@@ -424,28 +425,47 @@ QWidget *WorkbenchPage::createReceiveSettingsSection()
424425
m_displayModeSegment->setCurrentItem(QStringLiteral("text"));
425426
root->addWidget(m_displayModeSegment);
426427

427-
root->addWidget(createCheckRow({QStringLiteral("保存接收"), QStringLiteral("自动滚动")},
428-
{&m_saveReceiveCheck, &m_autoScrollCheck},
429-
section));
430-
root->addWidget(createCheckRow({QStringLiteral("时间戳"), QStringLiteral("暂停显示")},
431-
{&m_timestampCheck, &m_pauseCheck},
432-
section));
428+
auto *receiveOptions = new QWidget(section);
429+
auto *receiveOptionsGrid = new QGridLayout(receiveOptions);
430+
receiveOptionsGrid->setContentsMargins(0, 0, 0, 0);
431+
receiveOptionsGrid->setHorizontalSpacing(12);
432+
receiveOptionsGrid->setVerticalSpacing(6);
433+
receiveOptionsGrid->setColumnStretch(0, 1);
434+
receiveOptionsGrid->setColumnStretch(1, 1);
435+
m_saveReceiveCheck = new CheckBox(QStringLiteral("保存接收"), receiveOptions);
436+
m_autoScrollCheck = new CheckBox(QStringLiteral("自动滚动"), receiveOptions);
437+
m_timestampCheck = new CheckBox(QStringLiteral("时间戳"), receiveOptions);
438+
m_pauseCheck = new CheckBox(QStringLiteral("暂停显示"), receiveOptions);
439+
for(CheckBox *check : {m_saveReceiveCheck, m_autoScrollCheck, m_timestampCheck, m_pauseCheck}) {
440+
check->setMinimumWidth(0);
441+
check->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
442+
}
443+
receiveOptionsGrid->addWidget(m_saveReceiveCheck, 0, 0);
444+
receiveOptionsGrid->addWidget(m_autoScrollCheck, 0, 1);
445+
receiveOptionsGrid->addWidget(m_timestampCheck, 1, 0);
446+
receiveOptionsGrid->addWidget(m_pauseCheck, 1, 1);
447+
root->addWidget(receiveOptions);
433448

434449
auto *frameRowWidget = new QWidget(section);
435450
auto *frameRow = new QHBoxLayout(frameRowWidget);
436451
frameRow->setContentsMargins(0, 0, 0, 0);
437-
frameRow->setSpacing(10);
452+
frameRow->setSpacing(6);
438453
m_autoFrameBreakCheck = new CheckBox(QStringLiteral("自动断帧"), frameRowWidget);
439454
m_autoFrameBreakCheck->setMinimumWidth(0);
440455
m_autoFrameBreakCheck->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
441456
m_frameBreakIntervalSpin = new SpinBox(frameRowWidget);
442457
m_frameBreakIntervalSpin->setRange(1, 60000);
443458
m_frameBreakIntervalSpin->setValue(20);
444-
m_frameBreakIntervalSpin->setSuffix(QStringLiteral(" ms"));
459+
m_frameBreakIntervalSpin->setSymbolVisible(false);
445460
m_frameBreakIntervalSpin->setFixedHeight(CompactControlHeight);
446-
setFixedControlWidth(m_frameBreakIntervalSpin, 116);
461+
setFixedControlWidth(m_frameBreakIntervalSpin, 92);
462+
auto *frameBreakUnitLabel = new CaptionLabel(QStringLiteral("ms"), frameRowWidget);
463+
frameBreakUnitLabel->setFixedHeight(CompactControlHeight);
464+
frameBreakUnitLabel->setFixedWidth(22);
465+
frameBreakUnitLabel->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
447466
frameRow->addWidget(m_autoFrameBreakCheck);
448467
frameRow->addWidget(m_frameBreakIntervalSpin);
468+
frameRow->addWidget(frameBreakUnitLabel);
449469
root->addWidget(frameRowWidget);
450470

451471
auto *actionRow = new QHBoxLayout;
@@ -542,25 +562,29 @@ QWidget *WorkbenchPage::createSendSettingsSection()
542562
m_lineEndingCombo->setCurrentIndex(0);
543563
addFormRow(root, QStringLiteral("换行"), m_lineEndingCombo);
544564

545-
auto *txRowWidget = new QWidget(section);
546-
auto *txRow = new QHBoxLayout(txRowWidget);
547-
txRow->setContentsMargins(0, 0, 0, 0);
548-
txRow->setSpacing(10);
549-
m_hexSendCheck = new CheckBox(QStringLiteral("HEX 发送"), txRowWidget);
550-
m_hexSendCheck->setMinimumWidth(0);
551-
m_hexSendCheck->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
552-
m_showTxCheck = new CheckBox(QStringLiteral("显示发送字符串"), txRowWidget);
553-
m_showTxCheck->setMinimumWidth(0);
554-
m_showTxCheck->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
555-
m_txColorButton = new ColorPickerButton(defaultTxColor(), QStringLiteral("TX 颜色"), txRowWidget);
565+
auto *sendOptions = new QWidget(section);
566+
auto *sendOptionsGrid = new QGridLayout(sendOptions);
567+
sendOptionsGrid->setContentsMargins(0, 0, 0, 0);
568+
sendOptionsGrid->setHorizontalSpacing(12);
569+
sendOptionsGrid->setVerticalSpacing(6);
570+
sendOptionsGrid->setColumnStretch(0, 1);
571+
sendOptionsGrid->setColumnStretch(1, 1);
572+
m_hexSendCheck = new CheckBox(QStringLiteral("HEX 发送"), sendOptions);
573+
m_showTxCheck = new CheckBox(QStringLiteral("显示发送字符串"), sendOptions);
574+
m_loopCheck = new CheckBox(QStringLiteral("定时发送"), sendOptions);
575+
m_autoReconnectCheck = new CheckBox(QStringLiteral("自动重连"), sendOptions);
576+
for(CheckBox *check : {m_hexSendCheck, m_showTxCheck, m_loopCheck, m_autoReconnectCheck}) {
577+
check->setMinimumWidth(0);
578+
check->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
579+
}
580+
m_txColorButton = new ColorPickerButton(defaultTxColor(), QStringLiteral("TX 颜色"), sendOptions);
556581
m_txColorButton->setFixedSize(64, CompactControlHeight);
557-
txRow->addWidget(m_hexSendCheck);
558-
txRow->addWidget(m_showTxCheck);
559-
txRow->addWidget(m_txColorButton);
560-
root->addWidget(txRowWidget);
561-
root->addWidget(createCheckRow({QStringLiteral("定时发送"), QStringLiteral("自动重连")},
562-
{&m_loopCheck, &m_autoReconnectCheck},
563-
section));
582+
sendOptionsGrid->addWidget(m_hexSendCheck, 0, 0);
583+
sendOptionsGrid->addWidget(m_showTxCheck, 0, 1);
584+
sendOptionsGrid->addWidget(m_txColorButton, 0, 2);
585+
sendOptionsGrid->addWidget(m_loopCheck, 1, 0);
586+
sendOptionsGrid->addWidget(m_autoReconnectCheck, 1, 1);
587+
root->addWidget(sendOptions);
564588

565589
m_historyCombo = new ComboBox(section);
566590
makeCompactControl(m_historyCombo);

0 commit comments

Comments
 (0)