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/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