From 985fb31ba6da8aaace02c449fac8d54f9fc94a01 Mon Sep 17 00:00:00 2001 From: linull Date: Sun, 26 Oct 2025 15:21:56 +0800 Subject: [PATCH 1/2] feat: Add Linux package management and automated build support - Add Debian/Ubuntu DEB package build support - Add Arch Linux package build support - Implement multi-architecture parallel builds (amd64/arm64) - Add GitHub Actions automated build and release - Update README with Linux distribution installation instructions - Remove original release-related content --- .github/RELEASE_TEMPLATE.md | 51 ++++++ .github/workflows/build.yml | 306 +++++++++++++++++++++++++++++++++++ .gitignore | 13 +- .nuke/build.schema.json | 127 +++++++++++++++ .nuke/parameters.json | 1 + PKGBUILD | 39 +++++ README.md | 43 +++-- build/Build.cs | 284 +++++++++++++++++++++----------- scripts/build-arch.sh | 91 +++++++++++ scripts/build-deb.sh | 104 ++++++++++++ scripts/create-release.sh | 100 ++++++++++++ scripts/generate-pkgbuild.sh | 69 ++++++++ scripts/test-release.sh | 113 +++++++++++++ scripts/validate-workflow.sh | 70 ++++++++ 14 files changed, 1303 insertions(+), 108 deletions(-) create mode 100644 .github/RELEASE_TEMPLATE.md create mode 100644 .github/workflows/build.yml create mode 100644 .nuke/build.schema.json create mode 100644 .nuke/parameters.json create mode 100644 PKGBUILD create mode 100755 scripts/build-arch.sh create mode 100755 scripts/build-deb.sh create mode 100755 scripts/create-release.sh create mode 100755 scripts/generate-pkgbuild.sh create mode 100755 scripts/test-release.sh create mode 100755 scripts/validate-workflow.sh diff --git a/.github/RELEASE_TEMPLATE.md b/.github/RELEASE_TEMPLATE.md new file mode 100644 index 0000000..0da3748 --- /dev/null +++ b/.github/RELEASE_TEMPLATE.md @@ -0,0 +1,51 @@ +# Aictionary Release Template + +## 新功能 ✨ +- + +## 改进 🚀 +- + +## 修复 🐛 +- + +## 技术更新 🔧 +- + +## 下载说明 📥 + +| 平台 | 自包含版本 | 框架依赖版本 | +|------|------------|-------------| +| Windows AMD64 | `Aictionary-windows-amd64.zip` | 包含在zip中 | +| Windows ARM64 | `Aictionary-windows-arm64.zip` | 包含在zip中 | +| macOS Intel | `Aictionary-macos-intel.tar.gz` | 包含在tar.gz中 | +| macOS ARM64 | `Aictionary-macos-arm64.tar.gz` (包含DMG) | 包含在tar.gz中 | +| Linux AMD64 | `Aictionary-linux-amd64.tar.gz` | 包含在tar.gz中 | +| Linux ARM64 | `Aictionary-linux-arm64.tar.gz` | 包含在tar.gz中 | +| Debian包 | `aictionary_*.deb` | - | +| Arch包 | `aictionary-*.pkg.tar.zst` | - | + +### 安装说明 + +**macOS用户:** +- 推荐使用DMG文件安装 +- 将App拖入Applications文件夹 + +**Windows用户:** +- 解压zip文件 +- 如果已安装.NET 8运行时,可选择框架依赖版本(体积更小) +- 否则使用自包含版本 + +**Linux用户:** +- Debian/Ubuntu: 使用 `sudo dpkg -i aictionary_*.deb` 安装deb包 +- Arch Linux: 使用 `sudo pacman -U aictionary-*.pkg.tar.zst` 安装 +- 其他发行版: 解压tar.gz文件直接运行 + +### 首次使用提示 💡 +- 别忘了在设置页配置OpenAI API Key +- 当本地词库缺少词条时会调用大模型补充释义 +- 支持键盘快捷键快速查询、复制和刷新 + +--- + +**完整更新日志**: https://github.com/your-username/Aictionary/compare/v{previous_version}...v{current_version} \ No newline at end of file diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..55ada98 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,306 @@ +name: Build and Package + +on: + push: + branches: [ main, master ] + tags: [ 'v*' ] + pull_request: + branches: [ main, master ] + +jobs: + build-linux: + strategy: + matrix: + arch: [amd64, arm64] + runs-on: ubuntu-22.04 + + steps: + - uses: actions/checkout@v4 + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '8.0.x' + + - name: Build Linux ${{ matrix.arch }} + run: | + dotnet run --project build/build.csproj -- PublishLinux${{ matrix.arch == 'amd64' && 'Amd64' || 'Arm64' }} + dotnet run --project build/build.csproj -- PublishLinux${{ matrix.arch == 'amd64' && 'Amd64' || 'Arm64' }}FrameworkDependent + + - name: Create Linux tar.gz archives + run: | + cd artifacts + if [ -d "linux-${{ matrix.arch }}" ]; then + tar -czf "Aictionary-linux-${{ matrix.arch }}.tar.gz" -C "linux-${{ matrix.arch }}" . + fi + if [ -d "linux-${{ matrix.arch }}-framework-dependent" ]; then + tar -czf "Aictionary-linux-${{ matrix.arch }}-framework-dependent.tar.gz" -C "linux-${{ matrix.arch }}-framework-dependent" . + fi + + - name: Upload Linux ${{ matrix.arch }} Artifacts + uses: actions/upload-artifact@v4 + with: + name: linux-${{ matrix.arch }}-packages + path: | + artifacts/*.tar.gz + artifacts/linux-${{ matrix.arch }}/ + artifacts/linux-${{ matrix.arch }}-framework-dependent/ + + build-windows: + strategy: + matrix: + arch: [amd64, arm64] + runs-on: windows-latest + + steps: + - uses: actions/checkout@v4 + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '8.0.x' + + - name: Build Windows ${{ matrix.arch }} + run: | + dotnet run --project build/build.csproj -- PublishWindows${{ matrix.arch == 'amd64' && 'Amd64' || 'Arm64' }} + dotnet run --project build/build.csproj -- PublishWindows${{ matrix.arch == 'amd64' && 'Amd64' || 'Arm64' }}FrameworkDependent + + - name: Create Windows ZIP archives + run: | + cd artifacts + if (Test-Path "windows-${{ matrix.arch }}") { + Compress-Archive -Path "windows-${{ matrix.arch }}\*" -DestinationPath "Aictionary-windows-${{ matrix.arch }}.zip" + } + if (Test-Path "windows-${{ matrix.arch }}-framework-dependent") { + Compress-Archive -Path "windows-${{ matrix.arch }}-framework-dependent\*" -DestinationPath "Aictionary-windows-${{ matrix.arch }}-framework-dependent.zip" + } + + - name: Upload Windows ${{ matrix.arch }} Artifacts + uses: actions/upload-artifact@v4 + with: + name: windows-${{ matrix.arch }}-packages + path: artifacts/*.zip + + build-macos: + strategy: + matrix: + arch: [intel, arm64] + runs-on: macos-latest + + steps: + - uses: actions/checkout@v4 + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '8.0.x' + + - name: Install create-dmg + run: brew install create-dmg + + - name: Build macOS ${{ matrix.arch }} + run: | + dotnet run --project build/build.csproj -- PublishMacOS${{ matrix.arch == 'intel' && 'Intel' || 'Arm64' }} + dotnet run --project build/build.csproj -- PublishMacOS${{ matrix.arch == 'intel' && 'Intel' || 'Arm64' }}FrameworkDependent + + - name: Create DMG for ${{ matrix.arch }} + run: | + if [ "${{ matrix.arch }}" = "arm64" ]; then + dotnet run --project build/build.csproj -- CreateMacOSDmg + else + # Create DMG for Intel too + dmg_file="artifacts/macos-${{ matrix.arch }}/Aictionary.dmg" + app_bundle="artifacts/macos-${{ matrix.arch }}/Aictionary.app" + if [ -d "$app_bundle" ]; then + create-dmg --volname "Aictionary" --window-pos 200 120 --window-size 800 400 --icon-size 128 --app-drop-link 600 185 "$dmg_file" "$app_bundle" + fi + fi + + - name: Upload macOS ${{ matrix.arch }} Artifacts + uses: actions/upload-artifact@v4 + with: + name: macos-${{ matrix.arch }}-packages + path: artifacts/macos-${{ matrix.arch }}/*.dmg + + build-deb: + needs: build-linux + strategy: + matrix: + type: [self-contained, framework-dependent] + runs-on: ubuntu-22.04 + + steps: + - uses: actions/checkout@v4 + + - name: Download Linux AMD64 Artifacts + uses: actions/download-artifact@v4 + with: + name: linux-amd64-packages + path: artifacts/ + + - name: Download Linux ARM64 Artifacts + uses: actions/download-artifact@v4 + with: + name: linux-arm64-packages + path: artifacts/ + + - name: Install packaging tools + run: | + sudo apt-get update + sudo apt-get install -y dpkg-dev fakeroot + + - name: Build DEB packages (${{ matrix.type }}) + run: | + chmod +x scripts/build-deb.sh + ./scripts/build-deb.sh ${{ matrix.type }} + + - name: Upload DEB packages + uses: actions/upload-artifact@v4 + with: + name: deb-packages-${{ matrix.type }} + path: "aictionary*${{ matrix.type == 'framework-dependent' && '-framework-dependent' || '' }}_*_*.deb" + + build-arch: + needs: build-linux + strategy: + matrix: + type: [self-contained, framework-dependent] + runs-on: ubuntu-22.04 + container: archlinux:latest + + steps: + - name: Install dependencies + run: | + pacman -Syu --noconfirm + pacman -S --noconfirm base-devel git dotnet-runtime + + - uses: actions/checkout@v4 + + - name: Download Linux AMD64 Artifacts + uses: actions/download-artifact@v4 + with: + name: linux-amd64-packages + path: artifacts/ + + - name: Download Linux ARM64 Artifacts + uses: actions/download-artifact@v4 + with: + name: linux-arm64-packages + path: artifacts/ + + - name: Create non-root user for makepkg + run: | + useradd -m builder + echo 'builder ALL=(ALL) NOPASSWD: ALL' >> /etc/sudoers + chown -R builder:builder . + + - name: Build Arch package (${{ matrix.type }}) + run: | + chmod +x scripts/build-arch.sh + sudo -u builder ./scripts/build-arch.sh ${{ matrix.type }} + + - name: Upload Arch package + uses: actions/upload-artifact@v4 + with: + name: arch-packages-${{ matrix.type }} + path: "aictionary*${{ matrix.type == 'framework-dependent' && '-framework-dependent' || '' }}-*-*.pkg.tar.zst" + + release: + if: startsWith(github.ref, 'refs/tags/v') + needs: [build-linux, build-windows, build-macos, build-deb, build-arch] + runs-on: ubuntu-latest + permissions: + contents: write + + steps: + - uses: actions/checkout@v4 + + - name: Download all artifacts + uses: actions/download-artifact@v4 + with: + path: release-artifacts + + - name: List artifacts + run: | + echo "Available artifacts:" + find release-artifacts -type f -name "*" | sort + + - name: Create Release + uses: actions/create-release@v1 + id: create_release + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: ${{ github.ref_name }} + release_name: Aictionary ${{ github.ref_name }} + draft: false + prerelease: false + body: | + ## 下载说明 + + ### 桌面应用 + | 平台 | 自包含版本 | 框架依赖版本 | + |------|------------|-------------| + | Windows AMD64 | Aictionary-windows-amd64.zip | Aictionary-windows-amd64-framework-dependent.zip | + | Windows ARM64 | Aictionary-windows-arm64.zip | Aictionary-windows-arm64-framework-dependent.zip | + | macOS Intel | Aictionary-intel.dmg | - | + | macOS ARM64 | Aictionary-arm64.dmg | - | + | Linux AMD64 | Aictionary-linux-amd64.tar.gz | Aictionary-linux-amd64-framework-dependent.tar.gz | + | Linux ARM64 | Aictionary-linux-arm64.tar.gz | Aictionary-linux-arm64-framework-dependent.tar.gz | + + ### Linux包管理器 + | 包类型 | 自包含版本 | 框架依赖版本 | + |--------|------------|-------------| + | Debian AMD64 | aictionary_*_amd64.deb | aictionary-framework-dependent_*_amd64.deb | + | Debian ARM64 | aictionary_*_arm64.deb | aictionary-framework-dependent_*_arm64.deb | + | Arch AMD64 | aictionary-*-x86_64.pkg.tar.zst | aictionary-framework-dependent-*-x86_64.pkg.tar.zst | + | Arch ARM64 | aictionary-*-aarch64.pkg.tar.zst | aictionary-framework-dependent-*-aarch64.pkg.tar.zst | + + **依赖说明:** + - **自包含版本**:无需安装.NET运行时,体积较大 + - **框架依赖版本**:需要安装.NET 8运行时,体积较小 + + **安装.NET 8运行时:** + - Windows: 从 [Microsoft官网](https://dotnet.microsoft.com/download/dotnet/8.0) 下载 + - Ubuntu/Debian: `sudo apt install dotnet-runtime-8.0` + - Arch Linux: `sudo pacman -S dotnet-runtime` + - macOS: `brew install dotnet` + + - name: Upload all release assets + run: | + # Upload Windows ZIP files + for zip in release-artifacts/windows-*-packages/*.zip; do + if [ -f "$zip" ]; then + gh release upload ${{ github.ref_name }} "$zip" --clobber + fi + done + + # Upload Linux tar.gz files + for tar in release-artifacts/linux-*-packages/*.tar.gz; do + if [ -f "$tar" ]; then + gh release upload ${{ github.ref_name }} "$tar" --clobber + fi + done + + # Upload macOS DMG files + for dmg in release-artifacts/macos-*-packages/*.dmg; do + if [ -f "$dmg" ]; then + gh release upload ${{ github.ref_name }} "$dmg" --clobber + fi + done + + # Upload DEB packages (both types) + for deb in release-artifacts/deb-packages-*/*.deb; do + if [ -f "$deb" ]; then + gh release upload ${{ github.ref_name }} "$deb" --clobber + fi + done + + # Upload Arch packages (both types) + for pkg in release-artifacts/arch-packages-*/*.pkg.tar.zst; do + if [ -f "$pkg" ]; then + gh release upload ${{ github.ref_name }} "$pkg" --clobber + fi + done + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/.gitignore b/.gitignore index 97c9838..afb5591 100644 --- a/.gitignore +++ b/.gitignore @@ -1,13 +1,18 @@ -bin/ -obj/ +**/bin/ +**/obj/ /packages/ riderModule.iml /_ReSharper.Caches/ Aictionary.sln.DotSettings.user .DS_Store + # NUKE Build artifacts/ -.nuke/ +.nuke/temp/ +.tmp build/bin/ build/obj/ -.tmp \ No newline at end of file + +# Keep NUKE configuration +!.nuke/build.schema.json +!.nuke/parameters.json \ No newline at end of file diff --git a/.nuke/build.schema.json b/.nuke/build.schema.json new file mode 100644 index 0000000..abc3fdf --- /dev/null +++ b/.nuke/build.schema.json @@ -0,0 +1,127 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "definitions": { + "Host": { + "type": "string", + "enum": [ + "AppVeyor", + "AzurePipelines", + "Bamboo", + "Bitbucket", + "Bitrise", + "GitHubActions", + "GitLab", + "Jenkins", + "Rider", + "SpaceAutomation", + "TeamCity", + "Terminal", + "TravisCI", + "VisualStudio", + "VSCode" + ] + }, + "ExecutableTarget": { + "type": "string", + "enum": [ + "Clean", + "Compile", + "CreateMacOSDmg", + "Publish", + "PublishLinuxAmd64", + "PublishLinuxAmd64FrameworkDependent", + "PublishLinuxArm64", + "PublishLinuxArm64FrameworkDependent", + "PublishMacOSArm64", + "PublishMacOSArm64FrameworkDependent", + "PublishMacOSIntel", + "PublishMacOSIntelFrameworkDependent", + "PublishWindowsAmd64", + "PublishWindowsAmd64FrameworkDependent", + "PublishWindowsArm64", + "PublishWindowsArm64FrameworkDependent", + "Restore" + ] + }, + "Verbosity": { + "type": "string", + "description": "", + "enum": [ + "Verbose", + "Normal", + "Minimal", + "Quiet" + ] + }, + "NukeBuild": { + "properties": { + "Continue": { + "type": "boolean", + "description": "Indicates to continue a previously failed build attempt" + }, + "Help": { + "type": "boolean", + "description": "Shows the help text for this build assembly" + }, + "Host": { + "description": "Host for execution. Default is 'automatic'", + "$ref": "#/definitions/Host" + }, + "NoLogo": { + "type": "boolean", + "description": "Disables displaying the NUKE logo" + }, + "Partition": { + "type": "string", + "description": "Partition to use on CI" + }, + "Plan": { + "type": "boolean", + "description": "Shows the execution plan (HTML)" + }, + "Profile": { + "type": "array", + "description": "Defines the profiles to load", + "items": { + "type": "string" + } + }, + "Root": { + "type": "string", + "description": "Root directory during build execution" + }, + "Skip": { + "type": "array", + "description": "List of targets to be skipped. Empty list skips all dependencies", + "items": { + "$ref": "#/definitions/ExecutableTarget" + } + }, + "Target": { + "type": "array", + "description": "List of targets to be invoked. Default is '{default_target}'", + "items": { + "$ref": "#/definitions/ExecutableTarget" + } + }, + "Verbosity": { + "description": "Logging verbosity during build execution. Default is 'Normal'", + "$ref": "#/definitions/Verbosity" + } + } + } + }, + "allOf": [ + { + "properties": { + "Configuration": { + "type": "string", + "description": "Configuration to build - Default is 'Release'" + } + } + }, + { + "$ref": "#/definitions/NukeBuild" + } + ] +} diff --git a/.nuke/parameters.json b/.nuke/parameters.json new file mode 100644 index 0000000..9e26dfe --- /dev/null +++ b/.nuke/parameters.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/PKGBUILD b/PKGBUILD new file mode 100644 index 0000000..191bfc1 --- /dev/null +++ b/PKGBUILD @@ -0,0 +1,39 @@ +# Maintainer: Aictionary Team +pkgname=aictionary +pkgver= +pkgrel=1 +pkgdesc="快速且异常好用的词典 App" +arch=('x86_64' 'aarch64') +url="https://github.com/username/Aictionary" +license=('MIT') +depends=('dotnet-runtime>=8.0') +provides=('aictionary') +conflicts=('aictionary-bin') + +source_x86_64=("aictionary-${pkgver}-x86_64::https://github.com/username/Aictionary/releases/download/v${pkgver}/Aictionary") +source_aarch64=("aictionary-${pkgver}-aarch64::https://github.com/username/Aictionary/releases/download/v${pkgver}/Aictionary") + +sha256sums_x86_64=('') +sha256sums_aarch64=('') + +package() { + # Install binary + install -Dm755 "${srcdir}/aictionary-${pkgver}-${CARCH}" "${pkgdir}/usr/bin/aictionary" + + # Install desktop file + install -Dm644 /dev/stdin "${pkgdir}/usr/share/applications/aictionary.desktop" << 'DESKTOP' +[Desktop Entry] +Name=Aictionary +Comment=快速且异常好用的词典 App +Exec=/usr/bin/aictionary +Icon=aictionary +Terminal=false +Type=Application +Categories=Office;Dictionary; +DESKTOP + + # Install icon if available + if [ -f "${srcdir}/AppIcon.png" ]; then + install -Dm644 "${srcdir}/AppIcon.png" "${pkgdir}/usr/share/pixmaps/aictionary.png" + fi +} diff --git a/README.md b/README.md index 6f11aea..3e8be80 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,6 @@ image - --- ## 功能特性 @@ -22,16 +21,39 @@ ## 下载与安装 -构建完成后,所有产物都会落在 `artifacts/` 目录下,也可在发布页直接获取: +构建完成后,所有产物都会落在 `artifacts/` 目录下: -| 平台 | 自包含产物 | 体积更小的框架依赖产物 | -| --- | --- | --- | -| macOS | `artifacts/macos/Aictionary.app`
`artifacts/macos/Aictionary.dmg` | `artifacts/macos-framework-dependent/Aictionary.app` | -| Windows | `artifacts/windows/Aictionary-win-x64` | `artifacts/windows-framework-dependent/Aictionary-win-x64` | +| 平台 | 自包含产物 | 体积更小的框架依赖产物 | +| ------- | ------------------------------------------------------------------------ | ------------------------------------------------------------ | +| macOS | `artifacts/macos/Aictionary.app
``artifacts/macos/Aictionary.dmg` | `artifacts/macos-framework-dependent/Aictionary.app` | +| Windows | `artifacts/windows/Aictionary-win-x64` | `artifacts/windows-framework-dependent/Aictionary-win-x64` | +| Linux | `artifacts/linux-amd64/Aictionary` | `artifacts/linux-amd64-framework-dependent/Aictionary` | - macOS 建议直接打开 `Aictionary.dmg`,将 App 拖入 `Applications` 即可; - Windows 可按是否安装 .NET 运行时选择对应文件夹; -- Linux 用户可使用源码编译运行(参考下文“开发构建”)。 +- Linux 用户可直接运行可执行文件或使用源码编译(参考下文"开发构建")。 + +### Linux 发行版包管理 + +**Debian/Ubuntu 系统:** +```bash +# 构建 DEB 包 +./scripts/build-deb.sh # 自包含版本 +./scripts/build-deb.sh framework-dependent # 框架依赖版本 + +# 安装 +sudo dpkg -i aictionary_*.deb +``` + +**Arch Linux 系统:** +```bash +# 构建 Arch 包 +./scripts/build-arch.sh # 自包含版本 +./scripts/build-arch.sh framework-dependent # 框架依赖版本 + +# 安装 +sudo pacman -U aictionary-*.pkg.tar.zst +``` > 初次使用别忘了在设置页配置 OpenAI API Key,当本地词库缺少词条时会调用大模型补充释义。 @@ -49,7 +71,7 @@ ## 开发构建 -项目使用 [NUKE](https://nuke.build/) 编排构建流程,运行以下命令即可生成全部发行包: +项目使用 [NUKE](https://nuke.build/) 编排构建流程,运行以下命令即可生成全部发行包,注意构建之前要保证电脑中有dotnet-sdk: ```bash dotnet run --project build/build.csproj @@ -58,7 +80,8 @@ dotnet run --project build/build.csproj 执行后会在 `artifacts/` 目录生成: - macOS 自包含 App、框架依赖 App 以及 DMG 安装包; -- Windows 自包含与框架依赖版本。 +- Windows 自包含与框架依赖版本; +- Linux 自包含与框架依赖版本。 如需自定义流程,可阅读 `build/Build.cs` 中各个 target 的实现。 @@ -66,4 +89,4 @@ dotnet run --project build/build.csproj - 欢迎通过 Issue 反馈问题、分享改进想法; - PR 亦非常欢迎,请附上截图或说明,便于快速审阅; -- 希望扩展词库、适配更多平台或接入新的大模型?一起来讨论吧! +- 希望扩展词库、适配更多平台或接入新的大模型?一起来讨论吧! \ No newline at end of file diff --git a/build/Build.cs b/build/Build.cs index b604d94..6b5c4fe 100644 --- a/build/Build.cs +++ b/build/Build.cs @@ -16,11 +16,25 @@ class Build : NukeBuild AbsolutePath SourceDirectory => RootDirectory / "Aictionary"; AbsolutePath ArtifactsDirectory => RootDirectory / "artifacts"; - AbsolutePath WindowsArtifactDirectory => ArtifactsDirectory / "windows"; - AbsolutePath WindowsFrameworkArtifactDirectory => ArtifactsDirectory / "windows-framework-dependent"; - AbsolutePath MacOSArtifactDirectory => ArtifactsDirectory / "macos"; - AbsolutePath MacOSFrameworkArtifactDirectory => ArtifactsDirectory / "macos-framework-dependent"; - AbsolutePath MacOSDmgFile => MacOSArtifactDirectory / "Aictionary.dmg"; + + // Windows + AbsolutePath WindowsAmd64Directory => ArtifactsDirectory / "windows-amd64"; + AbsolutePath WindowsAmd64FrameworkDirectory => ArtifactsDirectory / "windows-amd64-framework-dependent"; + AbsolutePath WindowsArm64Directory => ArtifactsDirectory / "windows-arm64"; + AbsolutePath WindowsArm64FrameworkDirectory => ArtifactsDirectory / "windows-arm64-framework-dependent"; + + // macOS + AbsolutePath MacOSIntelDirectory => ArtifactsDirectory / "macos-intel"; + AbsolutePath MacOSIntelFrameworkDirectory => ArtifactsDirectory / "macos-intel-framework-dependent"; + AbsolutePath MacOSArm64Directory => ArtifactsDirectory / "macos-arm64"; + AbsolutePath MacOSArm64FrameworkDirectory => ArtifactsDirectory / "macos-arm64-framework-dependent"; + + // Linux + AbsolutePath LinuxAmd64Directory => ArtifactsDirectory / "linux-amd64"; + AbsolutePath LinuxAmd64FrameworkDirectory => ArtifactsDirectory / "linux-amd64-framework-dependent"; + AbsolutePath LinuxArm64Directory => ArtifactsDirectory / "linux-arm64"; + AbsolutePath LinuxArm64FrameworkDirectory => ArtifactsDirectory / "linux-arm64-framework-dependent"; + AbsolutePath ProjectFile => SourceDirectory / "Aictionary.csproj"; Target Clean => _ => _ @@ -68,8 +82,7 @@ class Build : NukeBuild .EnableNoRestore()); }); - Target PublishWindows => _ => _ - .DependsOn(Clean) + Target PublishWindowsAmd64 => _ => _ .Executes(() => { DotNetPublish(s => s @@ -79,144 +92,168 @@ class Build : NukeBuild .SetSelfContained(true) .SetPublishSingleFile(true) .SetPublishTrimmed(false) - .SetOutput(WindowsArtifactDirectory / "Aictionary-win-x64")); + .SetOutput(WindowsAmd64Directory)); - Serilog.Log.Information($"Windows artifact published to: {WindowsArtifactDirectory}"); + Serilog.Log.Information($"Windows AMD64 artifact published to: {WindowsAmd64Directory}"); }); - Target PublishMacOS => _ => _ - .DependsOn(Clean) + Target PublishWindowsArm64 => _ => _ .Executes(() => { - var publishOutput = MacOSArtifactDirectory / "publish"; - var appBundlePath = MacOSArtifactDirectory / "Aictionary.app"; - var contentsPath = appBundlePath / "Contents"; - var macOsPath = contentsPath / "MacOS"; - - if (Directory.Exists(publishOutput)) - Directory.Delete(publishOutput, true); - DotNetPublish(s => s .SetProject(ProjectFile) .SetConfiguration(Configuration) - .SetRuntime("osx-arm64") + .SetRuntime("win-arm64") .SetSelfContained(true) .SetPublishSingleFile(true) .SetPublishTrimmed(false) - .SetOutput(publishOutput)); - - if (Directory.Exists(appBundlePath)) - Directory.Delete(appBundlePath, true); - - Directory.CreateDirectory(macOsPath); - CopyDirectoryContents(publishOutput, macOsPath); - Directory.Delete(publishOutput, true); + .SetOutput(WindowsArm64Directory)); - // Copy Info.plist and icon - var infoSource = SourceDirectory / "Info.plist"; - var iconSource = SourceDirectory / "Assets" / "AppIcon.icns"; - var resourcesDir = contentsPath / "Resources"; - var infoTarget = contentsPath / "Info.plist"; - - Directory.CreateDirectory(resourcesDir); - if (File.Exists(infoSource)) - { - File.Copy(infoSource, infoTarget, true); - } - if (File.Exists(iconSource)) - { - File.Copy(iconSource, resourcesDir / "AppIcon.icns", true); - } - - Serilog.Log.Information($"macOS artifact published to: {MacOSArtifactDirectory}"); + Serilog.Log.Information($"Windows ARM64 artifact published to: {WindowsArm64Directory}"); }); - Target PublishMacOSFrameworkDependent => _ => _ - .DependsOn(Clean) + Target PublishWindowsAmd64FrameworkDependent => _ => _ .Executes(() => { - var publishOutput = MacOSFrameworkArtifactDirectory / "publish"; - var appBundlePath = MacOSFrameworkArtifactDirectory / "Aictionary.app"; - var contentsPath = appBundlePath / "Contents"; - var macOsPath = contentsPath / "MacOS"; + DotNetPublish(s => s + .SetProject(ProjectFile) + .SetConfiguration(Configuration) + .SetRuntime("win-x64") + .DisableSelfContained() + .DisablePublishSingleFile() + .SetPublishTrimmed(false) + .SetOutput(WindowsAmd64FrameworkDirectory)); - if (Directory.Exists(publishOutput)) - Directory.Delete(publishOutput, true); + Serilog.Log.Information($"Windows AMD64 framework-dependent artifact published to: {WindowsAmd64FrameworkDirectory}"); + }); + Target PublishWindowsArm64FrameworkDependent => _ => _ + .Executes(() => + { DotNetPublish(s => s .SetProject(ProjectFile) .SetConfiguration(Configuration) - .SetRuntime("osx-arm64") + .SetRuntime("win-arm64") .DisableSelfContained() .DisablePublishSingleFile() .SetPublishTrimmed(false) - .SetOutput(publishOutput)); + .SetOutput(WindowsArm64FrameworkDirectory)); - if (Directory.Exists(appBundlePath)) - Directory.Delete(appBundlePath, true); + Serilog.Log.Information($"Windows ARM64 framework-dependent artifact published to: {WindowsArm64FrameworkDirectory}"); + }); - Directory.CreateDirectory(macOsPath); - CopyDirectoryContents(publishOutput, macOsPath); - Directory.Delete(publishOutput, true); + Target PublishMacOSIntel => _ => _ + .Executes(() => + { + CreateMacOSApp(MacOSIntelDirectory, "osx-x64", "macOS Intel"); + }); - var infoSource = SourceDirectory / "Info.plist"; - var iconSource = SourceDirectory / "Assets" / "AppIcon.icns"; - var resourcesDir = contentsPath / "Resources"; - var infoTarget = contentsPath / "Info.plist"; + Target PublishMacOSArm64 => _ => _ + .Executes(() => + { + CreateMacOSApp(MacOSArm64Directory, "osx-arm64", "macOS ARM64"); + }); - Directory.CreateDirectory(resourcesDir); - if (File.Exists(infoSource)) - { - File.Copy(infoSource, infoTarget, true); - } - if (File.Exists(iconSource)) - { - File.Copy(iconSource, resourcesDir / "AppIcon.icns", true); - } + Target PublishMacOSIntelFrameworkDependent => _ => _ + .Executes(() => + { + CreateMacOSApp(MacOSIntelFrameworkDirectory, "osx-x64", "macOS Intel framework-dependent", false); + }); - Serilog.Log.Information($"macOS framework-dependent artifact published to: {MacOSFrameworkArtifactDirectory}"); + Target PublishMacOSArm64FrameworkDependent => _ => _ + .Executes(() => + { + CreateMacOSApp(MacOSArm64FrameworkDirectory, "osx-arm64", "macOS ARM64 framework-dependent", false); }); - Target Publish => _ => _ - .DependsOn(PublishWindows, - PublishMacOS, - PublishWindowsFrameworkDependent, - PublishMacOSFrameworkDependent, - CreateMacOSDmg) + + + Target PublishLinuxAmd64 => _ => _ .Executes(() => { - Serilog.Log.Information("All platforms published successfully!"); - Serilog.Log.Information($"Artifacts location: {ArtifactsDirectory}"); + DotNetPublish(s => s + .SetProject(ProjectFile) + .SetConfiguration(Configuration) + .SetRuntime("linux-x64") + .SetSelfContained(true) + .SetPublishSingleFile(true) + .SetPublishTrimmed(false) + .SetOutput(LinuxAmd64Directory)); + + Serilog.Log.Information($"Linux AMD64 artifact published to: {LinuxAmd64Directory}"); }); - Target PublishWindowsFrameworkDependent => _ => _ - .DependsOn(Clean) + Target PublishLinuxArm64 => _ => _ .Executes(() => { - var outputDirectory = WindowsFrameworkArtifactDirectory / "Aictionary-win-x64"; + DotNetPublish(s => s + .SetProject(ProjectFile) + .SetConfiguration(Configuration) + .SetRuntime("linux-arm64") + .SetSelfContained(true) + .SetPublishSingleFile(true) + .SetPublishTrimmed(false) + .SetOutput(LinuxArm64Directory)); + + Serilog.Log.Information($"Linux ARM64 artifact published to: {LinuxArm64Directory}"); + }); + Target PublishLinuxAmd64FrameworkDependent => _ => _ + .Executes(() => + { DotNetPublish(s => s .SetProject(ProjectFile) .SetConfiguration(Configuration) - .SetRuntime("win-x64") + .SetRuntime("linux-x64") + .DisableSelfContained() + .DisablePublishSingleFile() + .SetPublishTrimmed(false) + .SetOutput(LinuxAmd64FrameworkDirectory)); + + Serilog.Log.Information($"Linux AMD64 framework-dependent artifact published to: {LinuxAmd64FrameworkDirectory}"); + }); + + Target PublishLinuxArm64FrameworkDependent => _ => _ + .Executes(() => + { + DotNetPublish(s => s + .SetProject(ProjectFile) + .SetConfiguration(Configuration) + .SetRuntime("linux-arm64") .DisableSelfContained() .DisablePublishSingleFile() .SetPublishTrimmed(false) - .SetOutput(outputDirectory)); + .SetOutput(LinuxArm64FrameworkDirectory)); - Serilog.Log.Information($"Windows framework-dependent artifact published to: {WindowsFrameworkArtifactDirectory}"); + Serilog.Log.Information($"Linux ARM64 framework-dependent artifact published to: {LinuxArm64FrameworkDirectory}"); + }); + + Target Publish => _ => _ + .DependsOn(Clean) + .DependsOn(PublishWindowsAmd64, PublishWindowsArm64, + PublishMacOSIntel, PublishMacOSArm64, + PublishLinuxAmd64, PublishLinuxArm64, + PublishWindowsAmd64FrameworkDependent, PublishWindowsArm64FrameworkDependent, + PublishMacOSIntelFrameworkDependent, PublishMacOSArm64FrameworkDependent, + PublishLinuxAmd64FrameworkDependent, PublishLinuxArm64FrameworkDependent, + CreateMacOSDmg) + .Executes(() => + { + Serilog.Log.Information("All platforms published successfully!"); + Serilog.Log.Information($"Artifacts location: {ArtifactsDirectory}"); }); Target CreateMacOSDmg => _ => _ - .DependsOn(PublishMacOS) + .DependsOn(PublishMacOSArm64) .Executes(() => { - if (File.Exists(MacOSDmgFile)) + var dmgFile = MacOSArm64Directory / "Aictionary.dmg"; + if (File.Exists(dmgFile)) { - File.Delete(MacOSDmgFile); + File.Delete(dmgFile); } - var appBundlePath = MacOSArtifactDirectory / "Aictionary.app"; + var appBundlePath = MacOSArm64Directory / "Aictionary.app"; var arguments = string.Join(" ", new[] { @@ -225,16 +262,75 @@ class Build : NukeBuild "--window-size 800 400", "--icon-size 128", "--app-drop-link 600 185", - $"\"{MacOSDmgFile}\"", + $"\"{dmgFile}\"", $"\"{appBundlePath}\"" }); ProcessTasks.StartProcess("create-dmg", arguments) .AssertZeroExitCode(); - Serilog.Log.Information($"macOS DMG created at: {MacOSDmgFile}"); + Serilog.Log.Information($"macOS DMG created at: {dmgFile}"); }); + void CreateMacOSApp(AbsolutePath outputDirectory, string runtime, string logName, bool selfContained = true) + { + var publishOutput = outputDirectory / "publish"; + var appBundlePath = outputDirectory / "Aictionary.app"; + var contentsPath = appBundlePath / "Contents"; + var macOsPath = contentsPath / "MacOS"; + + if (Directory.Exists(publishOutput)) + Directory.Delete(publishOutput, true); + + if (selfContained) + { + DotNetPublish(s => s + .SetProject(ProjectFile) + .SetConfiguration(Configuration) + .SetRuntime(runtime) + .SetSelfContained(true) + .SetPublishSingleFile(true) + .SetPublishTrimmed(false) + .SetOutput(publishOutput)); + } + else + { + DotNetPublish(s => s + .SetProject(ProjectFile) + .SetConfiguration(Configuration) + .SetRuntime(runtime) + .DisableSelfContained() + .DisablePublishSingleFile() + .SetPublishTrimmed(false) + .SetOutput(publishOutput)); + } + + if (Directory.Exists(appBundlePath)) + Directory.Delete(appBundlePath, true); + + Directory.CreateDirectory(macOsPath); + CopyDirectoryContents(publishOutput, macOsPath); + Directory.Delete(publishOutput, true); + + // Copy Info.plist and icon + var infoSource = SourceDirectory / "Info.plist"; + var iconSource = SourceDirectory / "Assets" / "AppIcon.icns"; + var resourcesDir = contentsPath / "Resources"; + var infoTarget = contentsPath / "Info.plist"; + + Directory.CreateDirectory(resourcesDir); + if (File.Exists(infoSource)) + { + File.Copy(infoSource, infoTarget, true); + } + if (File.Exists(iconSource)) + { + File.Copy(iconSource, resourcesDir / "AppIcon.icns", true); + } + + Serilog.Log.Information($"{logName} artifact published to: {outputDirectory}"); + } + static void CopyDirectoryContents(string source, string destination) { Directory.CreateDirectory(destination); diff --git a/scripts/build-arch.sh b/scripts/build-arch.sh new file mode 100755 index 0000000..1334715 --- /dev/null +++ b/scripts/build-arch.sh @@ -0,0 +1,91 @@ +#!/bin/bash +set -e + +TYPE=${1:-self-contained} +echo "Building Arch Linux package ($TYPE)..." + +# Set package name and dependencies based on type +PKG_NAME="aictionary" +DEPENDS="depends=()" +if [ "$TYPE" = "framework-dependent" ]; then + PKG_NAME="aictionary-framework-dependent" + DEPENDS="depends=('dotnet-runtime')" +fi + +# Create PKGBUILD based on type +cat > PKGBUILD << EOF +# Maintainer: Aictionary Team +pkgname=$PKG_NAME +pkgver=1.0.0 +pkgrel=1 +pkgdesc="快速且异常好用的词典 App ($TYPE)" +arch=('x86_64' 'aarch64') +url="https://github.com/username/Aictionary" +license=('MIT') +$DEPENDS +provides=('aictionary') + +package() { + # Create directories + install -dm755 "\${pkgdir}/usr/bin" + install -dm755 "\${pkgdir}/usr/share/applications" + install -dm755 "\${pkgdir}/usr/share/pixmaps" + + # Determine source directory based on type and architecture + local src_dir="" + if [ "$TYPE" = "framework-dependent" ]; then + if [ "\$CARCH" = "x86_64" ]; then + src_dir="\${srcdir}/../artifacts/linux-amd64-framework-dependent" + elif [ "\$CARCH" = "aarch64" ]; then + src_dir="\${srcdir}/../artifacts/linux-arm64-framework-dependent" + fi + else + if [ "\$CARCH" = "x86_64" ]; then + src_dir="\${srcdir}/../artifacts/linux-amd64" + elif [ "\$CARCH" = "aarch64" ]; then + src_dir="\${srcdir}/../artifacts/linux-arm64" + fi + fi + + # Install binary + if [ -f "\$src_dir/Aictionary" ]; then + install -Dm755 "\$src_dir/Aictionary" "\${pkgdir}/usr/bin/aictionary" + fi + + # Install additional files for framework-dependent version + if [ "$TYPE" = "framework-dependent" ]; then + find "\$src_dir" -name "*.dll" -o -name "*.so" -o -name "*.json" -o -name "*.pdb" | while read file; do + if [ -f "\$file" ]; then + install -Dm644 "\$file" "\${pkgdir}/usr/bin/\$(basename "\$file")" + fi + done + # Copy Assets directory if exists + if [ -d "\$src_dir/Assets" ]; then + cp -r "\$src_dir/Assets" "\${pkgdir}/usr/bin/" + fi + fi + + # Install desktop file + cat > "\${pkgdir}/usr/share/applications/$PKG_NAME.desktop" << 'DESKTOP' +[Desktop Entry] +Name=Aictionary +Comment=快速且异常好用的词典 App +Exec=/usr/bin/aictionary +Icon=aictionary +Terminal=false +Type=Application +Categories=Office;Dictionary; +DESKTOP + + # Install icon if available + if [ -f "\${srcdir}/../Aictionary/Assets/AppIcon.png" ]; then + install -Dm644 "\${srcdir}/../Aictionary/Assets/AppIcon.png" "\${pkgdir}/usr/share/pixmaps/aictionary.png" + fi +} +EOF + +# Build package (skip dependency checks since we're in a container) +makepkg -f --noconfirm --nodeps + +echo "Arch Linux package ($TYPE) built successfully!" +ls -la *.pkg.tar.zst \ No newline at end of file diff --git a/scripts/build-deb.sh b/scripts/build-deb.sh new file mode 100755 index 0000000..8abf0e4 --- /dev/null +++ b/scripts/build-deb.sh @@ -0,0 +1,104 @@ +#!/bin/bash +set -e + +TYPE=${1:-self-contained} +VERSION=$(grep -o '[^<]*' Aictionary/Aictionary.csproj | sed 's///') +if [ -z "$VERSION" ]; then + VERSION="1.0.0" +fi + +build_deb() { + local arch=$1 + local artifact_dir=$2 + local deb_arch=$3 + local package_type=$4 + + echo "Building DEB package for $arch ($package_type)..." + + # Set package name and dependencies based on type + local pkg_name="aictionary" + local depends="" + if [ "$package_type" = "framework-dependent" ]; then + pkg_name="aictionary-framework-dependent" + depends="Depends: dotnet-runtime-8.0" + fi + + # Create package structure + local pkg_dir="${pkg_name}-${VERSION}-${arch}" + mkdir -p "$pkg_dir/DEBIAN" + mkdir -p "$pkg_dir/usr/bin" + mkdir -p "$pkg_dir/usr/share/applications" + mkdir -p "$pkg_dir/usr/share/pixmaps" + + # Copy binary + if [ -f "artifacts/$artifact_dir/Aictionary" ]; then + cp "artifacts/$artifact_dir/Aictionary" "$pkg_dir/usr/bin/" + chmod +x "$pkg_dir/usr/bin/Aictionary" + else + echo "Warning: Binary not found in artifacts/$artifact_dir/" + return 1 + fi + + # Copy additional files for framework-dependent version + if [ "$package_type" = "framework-dependent" ]; then + find "artifacts/$artifact_dir" -name "*.dll" -o -name "*.so" -o -name "*.json" -o -name "*.pdb" | while read file; do + if [ -f "$file" ]; then + cp "$file" "$pkg_dir/usr/bin/" + fi + done + # Copy Assets directory if exists + if [ -d "artifacts/$artifact_dir/Assets" ]; then + cp -r "artifacts/$artifact_dir/Assets" "$pkg_dir/usr/bin/" + fi + fi + + # Create control file + { + echo "Package: $pkg_name" + echo "Version: $VERSION" + echo "Section: utils" + echo "Priority: optional" + echo "Architecture: $deb_arch" + echo "Maintainer: Aictionary Team" + if [ -n "$depends" ]; then + echo "$depends" + fi + echo "Description: 快速且异常好用的词典 App ($package_type)" + echo " 基于 Avalonia 框架的跨平台词典应用程序,支持本地词库和大语言模型驱动的词义生成。" + } > "$pkg_dir/DEBIAN/control" + + # Create desktop file + cat > "$pkg_dir/usr/share/applications/${pkg_name}.desktop" << EOF +[Desktop Entry] +Name=Aictionary +Comment=快速且异常好用的词典 App +Exec=/usr/bin/Aictionary +Icon=aictionary +Terminal=false +Type=Application +Categories=Office;Dictionary; +EOF + + # Copy icon if exists + if [ -f "Aictionary/Assets/AppIcon.png" ]; then + cp "Aictionary/Assets/AppIcon.png" "$pkg_dir/usr/share/pixmaps/aictionary.png" + fi + + # Build package + fakeroot dpkg-deb --build "$pkg_dir" + mv "${pkg_dir}.deb" "${pkg_name}_${VERSION}_${deb_arch}.deb" + rm -rf "$pkg_dir" + + echo "Created: ${pkg_name}_${VERSION}_${deb_arch}.deb" +} + +# Determine artifact directories based on type +if [ "$TYPE" = "framework-dependent" ]; then + build_deb "amd64" "linux-amd64-framework-dependent" "amd64" "framework-dependent" + build_deb "arm64" "linux-arm64-framework-dependent" "arm64" "framework-dependent" +else + build_deb "amd64" "linux-amd64" "amd64" "self-contained" + build_deb "arm64" "linux-arm64" "arm64" "self-contained" +fi + +echo "DEB packages ($TYPE) built successfully!" \ No newline at end of file diff --git a/scripts/create-release.sh b/scripts/create-release.sh new file mode 100755 index 0000000..19d9123 --- /dev/null +++ b/scripts/create-release.sh @@ -0,0 +1,100 @@ +#!/bin/bash + +# 快速创建release的脚本 + +set -e + +# 颜色定义 +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +echo -e "${BLUE}🚀 Aictionary Release Creator${NC}" +echo "" + +# 检查是否在git仓库中 +if ! git rev-parse --git-dir > /dev/null 2>&1; then + echo -e "${RED}❌ 错误:当前目录不是git仓库${NC}" + exit 1 +fi + +# 检查是否有未提交的更改 +if ! git diff-index --quiet HEAD --; then + echo -e "${YELLOW}⚠️ 警告:有未提交的更改${NC}" + echo "请先提交所有更改后再创建release" + git status --short + exit 1 +fi + +# 获取当前分支 +CURRENT_BRANCH=$(git branch --show-current) +if [ "$CURRENT_BRANCH" != "main" ] && [ "$CURRENT_BRANCH" != "master" ]; then + echo -e "${YELLOW}⚠️ 警告:当前不在main/master分支 (当前: $CURRENT_BRANCH)${NC}" + read -p "是否继续?(y/N): " -n 1 -r + echo + if [[ ! $REPLY =~ ^[Yy]$ ]]; then + exit 1 + fi +fi + +# 获取最新标签 +LATEST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "v0.0.0") +echo -e "${BLUE}📍 最新标签: $LATEST_TAG${NC}" + +# 提示输入新版本 +echo "" +echo "请输入新版本号 (格式: v1.0.0):" +read -p "版本号: " NEW_VERSION + +# 验证版本号格式 +if [[ ! $NEW_VERSION =~ ^v[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9]+)?$ ]]; then + echo -e "${RED}❌ 错误:版本号格式不正确${NC}" + echo "正确格式: v1.0.0 或 v1.0.0-beta" + exit 1 +fi + +# 检查标签是否已存在 +if git rev-parse "$NEW_VERSION" >/dev/null 2>&1; then + echo -e "${RED}❌ 错误:标签 $NEW_VERSION 已存在${NC}" + exit 1 +fi + +# 显示将要创建的release信息 +echo "" +echo -e "${GREEN}📋 Release信息:${NC}" +echo " 版本: $NEW_VERSION" +echo " 分支: $CURRENT_BRANCH" +echo " 提交: $(git rev-parse --short HEAD)" +echo "" + +# 确认创建 +read -p "确认创建release?(y/N): " -n 1 -r +echo +if [[ ! $REPLY =~ ^[Yy]$ ]]; then + echo "已取消" + exit 0 +fi + +# 创建标签 +echo -e "${BLUE}🏷️ 创建标签 $NEW_VERSION...${NC}" +git tag -a "$NEW_VERSION" -m "Release $NEW_VERSION" + +# 推送标签 +echo -e "${BLUE}📤 推送标签到远程仓库...${NC}" +git push origin "$NEW_VERSION" + +echo "" +echo -e "${GREEN}✅ Release创建成功!${NC}" +echo "" +echo -e "${BLUE}📝 接下来的步骤:${NC}" +echo "1. 查看GitHub Actions页面确认构建状态" +echo "2. 构建完成后,在GitHub Releases页面编辑发布说明" +echo "3. 可以使用 .github/RELEASE_TEMPLATE.md 作为模板" +echo "" +echo -e "${BLUE}🔗 相关链接:${NC}" +echo " GitHub Actions: https://github.com/$(git config --get remote.origin.url | sed 's/.*github.com[:/]\([^.]*\).*/\1/')/actions" +echo " Releases: https://github.com/$(git config --get remote.origin.url | sed 's/.*github.com[:/]\([^.]*\).*/\1/')/releases" +echo "" +echo -e "${YELLOW}⏳ 构建大约需要10-15分钟完成${NC}" \ No newline at end of file diff --git a/scripts/generate-pkgbuild.sh b/scripts/generate-pkgbuild.sh new file mode 100755 index 0000000..8ed5f2b --- /dev/null +++ b/scripts/generate-pkgbuild.sh @@ -0,0 +1,69 @@ +#!/bin/bash +set -e + +# Extract version from project file +VERSION=$(grep -o '[^<]*' Aictionary/Aictionary.csproj | sed 's///' || echo "1.0.0") + +# Get git commit hash for pkgver +COMMIT_HASH=$(git rev-parse --short HEAD 2>/dev/null || echo "unknown") + +# Calculate checksums for artifacts +AMD64_SHA256="" +ARM64_SHA256="" + +if [ -f "artifacts/linux-amd64/Aictionary" ]; then + AMD64_SHA256=$(sha256sum artifacts/linux-amd64/Aictionary | cut -d' ' -f1) +fi + +if [ -f "artifacts/linux-arm64/Aictionary" ]; then + ARM64_SHA256=$(sha256sum artifacts/linux-arm64/Aictionary | cut -d' ' -f1) +fi + +# Generate PKGBUILD +cat > PKGBUILD << EOF +# Maintainer: Aictionary Team +pkgname=aictionary +pkgver=${VERSION} +pkgrel=1 +pkgdesc="快速且异常好用的词典 App" +arch=('x86_64' 'aarch64') +url="https://github.com/username/Aictionary" +license=('MIT') +depends=('dotnet-runtime>=8.0') +provides=('aictionary') +conflicts=('aictionary-bin') + +source_x86_64=("aictionary-\${pkgver}-x86_64::https://github.com/username/Aictionary/releases/download/v\${pkgver}/Aictionary") +source_aarch64=("aictionary-\${pkgver}-aarch64::https://github.com/username/Aictionary/releases/download/v\${pkgver}/Aictionary") + +sha256sums_x86_64=('${AMD64_SHA256}') +sha256sums_aarch64=('${ARM64_SHA256}') + +package() { + # Install binary + install -Dm755 "\${srcdir}/aictionary-\${pkgver}-\${CARCH}" "\${pkgdir}/usr/bin/aictionary" + + # Install desktop file + install -Dm644 /dev/stdin "\${pkgdir}/usr/share/applications/aictionary.desktop" << 'DESKTOP' +[Desktop Entry] +Name=Aictionary +Comment=快速且异常好用的词典 App +Exec=/usr/bin/aictionary +Icon=aictionary +Terminal=false +Type=Application +Categories=Office;Dictionary; +DESKTOP + + # Install icon if available + if [ -f "\${srcdir}/AppIcon.png" ]; then + install -Dm644 "\${srcdir}/AppIcon.png" "\${pkgdir}/usr/share/pixmaps/aictionary.png" + fi +} +EOF + +echo "PKGBUILD generated successfully!" +echo "Version: $VERSION" +echo "Commit: $COMMIT_HASH" +echo "AMD64 SHA256: $AMD64_SHA256" +echo "ARM64 SHA256: $ARM64_SHA256" \ No newline at end of file diff --git a/scripts/test-release.sh b/scripts/test-release.sh new file mode 100755 index 0000000..54dabb1 --- /dev/null +++ b/scripts/test-release.sh @@ -0,0 +1,113 @@ +#!/bin/bash + +# 测试release功能的脚本 +# 此脚本用于本地测试release工作流的各个步骤 + +set -e + +echo "🚀 开始测试 Aictionary Release 功能" + +# 检查必要的工具 +echo "📋 检查必要工具..." +command -v git >/dev/null 2>&1 || { echo "❌ 需要安装 git"; exit 1; } +command -v dotnet >/dev/null 2>&1 || { echo "❌ 需要安装 .NET SDK"; exit 1; } + +# 获取当前版本信息 +CURRENT_BRANCH=$(git branch --show-current) +LATEST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "v0.0.0") +echo "📍 当前分支: $CURRENT_BRANCH" +echo "📍 最新标签: $LATEST_TAG" + +# 创建测试标签 +TEST_TAG="v1.0.0-test-$(date +%s)" +echo "🏷️ 创建测试标签: $TEST_TAG" + +# 模拟构建过程 +echo "🔨 开始构建测试..." +cd "$(dirname "$0")/.." + +# 清理之前的构建 +if [ -d "artifacts" ]; then + echo "🧹 清理旧的构建产物..." + rm -rf artifacts +fi + +# 运行构建 +echo "⚙️ 执行构建命令..." +dotnet run --project build/build.csproj -- Clean + +# 测试单个平台构建(Linux AMD64) +echo "🐧 测试 Linux AMD64 构建..." +dotnet run --project build/build.csproj -- PublishLinuxAmd64 + +# 检查构建结果 +if [ -d "artifacts/linux-amd64" ]; then + echo "✅ Linux AMD64 构建成功" + ls -la artifacts/linux-amd64/ +else + echo "❌ Linux AMD64 构建失败" + exit 1 +fi + +# 测试打包功能 +echo "📦 测试打包功能..." +cd artifacts +if [ -d "linux-amd64" ]; then + echo "📦 创建 Linux AMD64 压缩包..." + tar -czf Aictionary-linux-amd64-test.tar.gz linux-amd64/ + if [ -f "Aictionary-linux-amd64-test.tar.gz" ]; then + echo "✅ 压缩包创建成功: $(ls -lh Aictionary-linux-amd64-test.tar.gz)" + else + echo "❌ 压缩包创建失败" + exit 1 + fi +fi +cd .. + +# 测试可执行文件 +echo "🧪 测试可执行文件..." +EXECUTABLE="artifacts/linux-amd64/Aictionary" +if [ -f "$EXECUTABLE" ]; then + echo "✅ 可执行文件存在: $EXECUTABLE" + echo "📊 文件信息:" + ls -lh "$EXECUTABLE" + file "$EXECUTABLE" + + # 测试是否可以运行(显示帮助信息) + echo "🔍 测试运行(应该会因为没有显示而失败,这是正常的)..." + timeout 5s "$EXECUTABLE" --help 2>/dev/null || echo "⚠️ 程序需要图形界面,无法在命令行测试运行" +else + echo "❌ 可执行文件不存在" + exit 1 +fi + +# 模拟GitHub Release创建 +echo "📋 模拟 GitHub Release 信息..." +cat << EOF + +🎉 Release 测试完成! + +如果这是真实的release,将会创建以下内容: + +标签: $TEST_TAG +发布名称: Aictionary $TEST_TAG + +包含的文件: +$(find artifacts -name "*.tar.gz" -o -name "*.zip" -o -name "*.deb" -o -name "*.pkg.tar.zst" 2>/dev/null | sed 's/^/ - /') + +构建产物目录: +$(find artifacts -type d -maxdepth 1 | grep -v "^artifacts$" | sed 's/^/ - /') + +EOF + +# 清理测试文件 +echo "🧹 清理测试文件..." +rm -f artifacts/*.tar.gz + +echo "✅ Release 功能测试完成!" +echo "" +echo "📝 要创建真实的release,请执行:" +echo " git tag $TEST_TAG" +echo " git push origin $TEST_TAG" +echo "" +echo "⚠️ 注意:这将触发GitHub Actions自动构建和发布" \ No newline at end of file diff --git a/scripts/validate-workflow.sh b/scripts/validate-workflow.sh new file mode 100755 index 0000000..9b90675 --- /dev/null +++ b/scripts/validate-workflow.sh @@ -0,0 +1,70 @@ +#!/bin/bash + +# 验证GitHub Actions工作流配置的脚本 + +set -e + +echo "🔍 验证 GitHub Actions 工作流配置" + +WORKFLOW_FILE=".github/workflows/build.yml" + +if [ ! -f "$WORKFLOW_FILE" ]; then + echo "❌ 工作流文件不存在: $WORKFLOW_FILE" + exit 1 +fi + +echo "✅ 工作流文件存在" + +# 检查工作流语法 +echo "📋 检查工作流语法..." + +# 检查是否包含release job +if grep -q "release:" "$WORKFLOW_FILE"; then + echo "✅ 包含 release job" +else + echo "❌ 缺少 release job" + exit 1 +fi + +# 检查是否有tag触发器 +if grep -q "tags:" "$WORKFLOW_FILE"; then + echo "✅ 包含 tag 触发器" +else + echo "❌ 缺少 tag 触发器" + exit 1 +fi + +# 检查是否有必要的权限 +if grep -q "contents: write" "$WORKFLOW_FILE"; then + echo "✅ 包含必要的权限设置" +else + echo "❌ 缺少必要的权限设置" + exit 1 +fi + +# 检查是否有创建release的步骤 +if grep -q "create-release" "$WORKFLOW_FILE"; then + echo "✅ 包含创建 release 的步骤" +else + echo "❌ 缺少创建 release 的步骤" + exit 1 +fi + +# 检查是否有上传资产的步骤 +if grep -q "upload-release-asset" "$WORKFLOW_FILE"; then + echo "✅ 包含上传资产的步骤" +else + echo "❌ 缺少上传资产的步骤" + exit 1 +fi + +echo "" +echo "🎉 工作流配置验证完成!" +echo "" +echo "📝 要测试release功能,请执行以下步骤:" +echo "1. 确保代码已提交到main分支" +echo "2. 创建并推送标签:" +echo " git tag v1.0.0" +echo " git push origin v1.0.0" +echo "3. 查看GitHub Actions页面确认工作流运行" +echo "4. 检查Releases页面确认release创建成功" \ No newline at end of file From f42d63fd8f7e9e82e3f666147e35a7ae3ccdc1a3 Mon Sep 17 00:00:00 2001 From: linull Date: Sun, 26 Oct 2025 15:34:09 +0800 Subject: [PATCH 2/2] chore: Clean up test and release scripts --- scripts/create-release.sh | 100 ------------------------------- scripts/test-release.sh | 113 ----------------------------------- scripts/validate-workflow.sh | 70 ---------------------- 3 files changed, 283 deletions(-) delete mode 100755 scripts/create-release.sh delete mode 100755 scripts/test-release.sh delete mode 100755 scripts/validate-workflow.sh diff --git a/scripts/create-release.sh b/scripts/create-release.sh deleted file mode 100755 index 19d9123..0000000 --- a/scripts/create-release.sh +++ /dev/null @@ -1,100 +0,0 @@ -#!/bin/bash - -# 快速创建release的脚本 - -set -e - -# 颜色定义 -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -BLUE='\033[0;34m' -NC='\033[0m' # No Color - -echo -e "${BLUE}🚀 Aictionary Release Creator${NC}" -echo "" - -# 检查是否在git仓库中 -if ! git rev-parse --git-dir > /dev/null 2>&1; then - echo -e "${RED}❌ 错误:当前目录不是git仓库${NC}" - exit 1 -fi - -# 检查是否有未提交的更改 -if ! git diff-index --quiet HEAD --; then - echo -e "${YELLOW}⚠️ 警告:有未提交的更改${NC}" - echo "请先提交所有更改后再创建release" - git status --short - exit 1 -fi - -# 获取当前分支 -CURRENT_BRANCH=$(git branch --show-current) -if [ "$CURRENT_BRANCH" != "main" ] && [ "$CURRENT_BRANCH" != "master" ]; then - echo -e "${YELLOW}⚠️ 警告:当前不在main/master分支 (当前: $CURRENT_BRANCH)${NC}" - read -p "是否继续?(y/N): " -n 1 -r - echo - if [[ ! $REPLY =~ ^[Yy]$ ]]; then - exit 1 - fi -fi - -# 获取最新标签 -LATEST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "v0.0.0") -echo -e "${BLUE}📍 最新标签: $LATEST_TAG${NC}" - -# 提示输入新版本 -echo "" -echo "请输入新版本号 (格式: v1.0.0):" -read -p "版本号: " NEW_VERSION - -# 验证版本号格式 -if [[ ! $NEW_VERSION =~ ^v[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9]+)?$ ]]; then - echo -e "${RED}❌ 错误:版本号格式不正确${NC}" - echo "正确格式: v1.0.0 或 v1.0.0-beta" - exit 1 -fi - -# 检查标签是否已存在 -if git rev-parse "$NEW_VERSION" >/dev/null 2>&1; then - echo -e "${RED}❌ 错误:标签 $NEW_VERSION 已存在${NC}" - exit 1 -fi - -# 显示将要创建的release信息 -echo "" -echo -e "${GREEN}📋 Release信息:${NC}" -echo " 版本: $NEW_VERSION" -echo " 分支: $CURRENT_BRANCH" -echo " 提交: $(git rev-parse --short HEAD)" -echo "" - -# 确认创建 -read -p "确认创建release?(y/N): " -n 1 -r -echo -if [[ ! $REPLY =~ ^[Yy]$ ]]; then - echo "已取消" - exit 0 -fi - -# 创建标签 -echo -e "${BLUE}🏷️ 创建标签 $NEW_VERSION...${NC}" -git tag -a "$NEW_VERSION" -m "Release $NEW_VERSION" - -# 推送标签 -echo -e "${BLUE}📤 推送标签到远程仓库...${NC}" -git push origin "$NEW_VERSION" - -echo "" -echo -e "${GREEN}✅ Release创建成功!${NC}" -echo "" -echo -e "${BLUE}📝 接下来的步骤:${NC}" -echo "1. 查看GitHub Actions页面确认构建状态" -echo "2. 构建完成后,在GitHub Releases页面编辑发布说明" -echo "3. 可以使用 .github/RELEASE_TEMPLATE.md 作为模板" -echo "" -echo -e "${BLUE}🔗 相关链接:${NC}" -echo " GitHub Actions: https://github.com/$(git config --get remote.origin.url | sed 's/.*github.com[:/]\([^.]*\).*/\1/')/actions" -echo " Releases: https://github.com/$(git config --get remote.origin.url | sed 's/.*github.com[:/]\([^.]*\).*/\1/')/releases" -echo "" -echo -e "${YELLOW}⏳ 构建大约需要10-15分钟完成${NC}" \ No newline at end of file diff --git a/scripts/test-release.sh b/scripts/test-release.sh deleted file mode 100755 index 54dabb1..0000000 --- a/scripts/test-release.sh +++ /dev/null @@ -1,113 +0,0 @@ -#!/bin/bash - -# 测试release功能的脚本 -# 此脚本用于本地测试release工作流的各个步骤 - -set -e - -echo "🚀 开始测试 Aictionary Release 功能" - -# 检查必要的工具 -echo "📋 检查必要工具..." -command -v git >/dev/null 2>&1 || { echo "❌ 需要安装 git"; exit 1; } -command -v dotnet >/dev/null 2>&1 || { echo "❌ 需要安装 .NET SDK"; exit 1; } - -# 获取当前版本信息 -CURRENT_BRANCH=$(git branch --show-current) -LATEST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "v0.0.0") -echo "📍 当前分支: $CURRENT_BRANCH" -echo "📍 最新标签: $LATEST_TAG" - -# 创建测试标签 -TEST_TAG="v1.0.0-test-$(date +%s)" -echo "🏷️ 创建测试标签: $TEST_TAG" - -# 模拟构建过程 -echo "🔨 开始构建测试..." -cd "$(dirname "$0")/.." - -# 清理之前的构建 -if [ -d "artifacts" ]; then - echo "🧹 清理旧的构建产物..." - rm -rf artifacts -fi - -# 运行构建 -echo "⚙️ 执行构建命令..." -dotnet run --project build/build.csproj -- Clean - -# 测试单个平台构建(Linux AMD64) -echo "🐧 测试 Linux AMD64 构建..." -dotnet run --project build/build.csproj -- PublishLinuxAmd64 - -# 检查构建结果 -if [ -d "artifacts/linux-amd64" ]; then - echo "✅ Linux AMD64 构建成功" - ls -la artifacts/linux-amd64/ -else - echo "❌ Linux AMD64 构建失败" - exit 1 -fi - -# 测试打包功能 -echo "📦 测试打包功能..." -cd artifacts -if [ -d "linux-amd64" ]; then - echo "📦 创建 Linux AMD64 压缩包..." - tar -czf Aictionary-linux-amd64-test.tar.gz linux-amd64/ - if [ -f "Aictionary-linux-amd64-test.tar.gz" ]; then - echo "✅ 压缩包创建成功: $(ls -lh Aictionary-linux-amd64-test.tar.gz)" - else - echo "❌ 压缩包创建失败" - exit 1 - fi -fi -cd .. - -# 测试可执行文件 -echo "🧪 测试可执行文件..." -EXECUTABLE="artifacts/linux-amd64/Aictionary" -if [ -f "$EXECUTABLE" ]; then - echo "✅ 可执行文件存在: $EXECUTABLE" - echo "📊 文件信息:" - ls -lh "$EXECUTABLE" - file "$EXECUTABLE" - - # 测试是否可以运行(显示帮助信息) - echo "🔍 测试运行(应该会因为没有显示而失败,这是正常的)..." - timeout 5s "$EXECUTABLE" --help 2>/dev/null || echo "⚠️ 程序需要图形界面,无法在命令行测试运行" -else - echo "❌ 可执行文件不存在" - exit 1 -fi - -# 模拟GitHub Release创建 -echo "📋 模拟 GitHub Release 信息..." -cat << EOF - -🎉 Release 测试完成! - -如果这是真实的release,将会创建以下内容: - -标签: $TEST_TAG -发布名称: Aictionary $TEST_TAG - -包含的文件: -$(find artifacts -name "*.tar.gz" -o -name "*.zip" -o -name "*.deb" -o -name "*.pkg.tar.zst" 2>/dev/null | sed 's/^/ - /') - -构建产物目录: -$(find artifacts -type d -maxdepth 1 | grep -v "^artifacts$" | sed 's/^/ - /') - -EOF - -# 清理测试文件 -echo "🧹 清理测试文件..." -rm -f artifacts/*.tar.gz - -echo "✅ Release 功能测试完成!" -echo "" -echo "📝 要创建真实的release,请执行:" -echo " git tag $TEST_TAG" -echo " git push origin $TEST_TAG" -echo "" -echo "⚠️ 注意:这将触发GitHub Actions自动构建和发布" \ No newline at end of file diff --git a/scripts/validate-workflow.sh b/scripts/validate-workflow.sh deleted file mode 100755 index 9b90675..0000000 --- a/scripts/validate-workflow.sh +++ /dev/null @@ -1,70 +0,0 @@ -#!/bin/bash - -# 验证GitHub Actions工作流配置的脚本 - -set -e - -echo "🔍 验证 GitHub Actions 工作流配置" - -WORKFLOW_FILE=".github/workflows/build.yml" - -if [ ! -f "$WORKFLOW_FILE" ]; then - echo "❌ 工作流文件不存在: $WORKFLOW_FILE" - exit 1 -fi - -echo "✅ 工作流文件存在" - -# 检查工作流语法 -echo "📋 检查工作流语法..." - -# 检查是否包含release job -if grep -q "release:" "$WORKFLOW_FILE"; then - echo "✅ 包含 release job" -else - echo "❌ 缺少 release job" - exit 1 -fi - -# 检查是否有tag触发器 -if grep -q "tags:" "$WORKFLOW_FILE"; then - echo "✅ 包含 tag 触发器" -else - echo "❌ 缺少 tag 触发器" - exit 1 -fi - -# 检查是否有必要的权限 -if grep -q "contents: write" "$WORKFLOW_FILE"; then - echo "✅ 包含必要的权限设置" -else - echo "❌ 缺少必要的权限设置" - exit 1 -fi - -# 检查是否有创建release的步骤 -if grep -q "create-release" "$WORKFLOW_FILE"; then - echo "✅ 包含创建 release 的步骤" -else - echo "❌ 缺少创建 release 的步骤" - exit 1 -fi - -# 检查是否有上传资产的步骤 -if grep -q "upload-release-asset" "$WORKFLOW_FILE"; then - echo "✅ 包含上传资产的步骤" -else - echo "❌ 缺少上传资产的步骤" - exit 1 -fi - -echo "" -echo "🎉 工作流配置验证完成!" -echo "" -echo "📝 要测试release功能,请执行以下步骤:" -echo "1. 确保代码已提交到main分支" -echo "2. 创建并推送标签:" -echo " git tag v1.0.0" -echo " git push origin v1.0.0" -echo "3. 查看GitHub Actions页面确认工作流运行" -echo "4. 检查Releases页面确认release创建成功" \ No newline at end of file