From dcfd00e6e1cf6ea9507459ddd605ad6bad3204ce Mon Sep 17 00:00:00 2001 From: Tw93 Date: Sat, 23 Aug 2025 14:12:54 +0800 Subject: [PATCH] :sparkles: Support packaging of multiple systems including Linux/Windows --- bin/README.md | 29 ++++++++++++- bin/README_CN.md | 29 ++++++++++++- bin/builders/LinuxBuilder.ts | 63 +++++++++++++++++++++++++-- bin/builders/MacBuilder.ts | 84 ++++++++++++++++++++++++++++++++---- bin/builders/WinBuilder.ts | 82 +++++++++++++++++++++++++++++++++-- bin/cli.ts | 7 +-- bin/helpers/merge.ts | 15 ++++++- bin/types.ts | 3 +- 8 files changed, 288 insertions(+), 24 deletions(-) diff --git a/bin/README.md b/bin/README.md index 2e72ec3..13e5a25 100644 --- a/bin/README.md +++ b/bin/README.md @@ -19,6 +19,8 @@ npm install pake-cli -g 4. Microsoft Visual C++ 2013 Redistributable (x86) (optional) 5. Microsoft Visual C++ 2008 Redistributable (x86) (optional) + **For Windows on ARM (ARM64) support**: Install the C++ ARM64 build tools in Visual Studio Installer under "Individual Components" → "MSVC v143 - VS 2022 C++ ARM64 build tools". The system will automatically detect ARM64 architecture and build native ARM64 binaries. + - For Ubuntu users, execute the following commands to install the required libraries before compiling: ```bash @@ -188,12 +190,35 @@ Package the application to support both Intel and M1 chips, exclusively for macO #### [targets] -Choose the output package format, supporting `deb`, `appimage`, `rpm`. This option is only applicable to Linux and defaults to `deb`. +Specify the build target architecture or format: + +- **Linux**: `deb`, `appimage`, `deb-arm64`, `appimage-arm64` (default: `deb`) +- **Windows**: `x64`, `arm64` (auto-detects if not specified) +- **macOS**: `intel`, `apple`, `universal` (auto-detects if not specified) ```shell ---targets +--targets + +# Examples: +--targets arm64 # Windows ARM64 +--targets x64 # Windows x64 +--targets universal # macOS Universal (Intel + Apple Silicon) +--targets apple # macOS Apple Silicon only +--targets intel # macOS Intel only +--targets deb # Linux DEB package (x64) +--targets rpm # Linux RPM package (x64) +--targets appimage # Linux AppImage (x64) +--targets deb-arm64 # Linux DEB package (ARM64) +--targets rpm-arm64 # Linux RPM package (ARM64) +--targets appimage-arm64 # Linux AppImage (ARM64) ``` +**Note for Linux ARM64**: + +- Cross-compilation requires additional setup. Install `gcc-aarch64-linux-gnu` and configure environment variables for cross-compilation. +- ARM64 support enables Pake apps to run on ARM-based Linux devices, including Linux phones (postmarketOS, Ubuntu Touch), Raspberry Pi, and other ARM64 Linux systems. +- Use `--target appimage-arm64` for portable ARM64 applications that work across different ARM64 Linux distributions. + #### [user-agent] Customize the browser user agent. Default is empty. diff --git a/bin/README_CN.md b/bin/README_CN.md index 657a41a..5507cf9 100644 --- a/bin/README_CN.md +++ b/bin/README_CN.md @@ -19,6 +19,8 @@ npm install pake-cli -g 4. Microsoft Visual C++ 2013 Redistributable (x86)(可选) 5. Microsoft Visual C++ 2008 Redistributable (x86)(可选) + **Windows ARM(ARM64)支持**:在 Visual Studio Installer 中的"单个组件"下安装"MSVC v143 - VS 2022 C++ ARM64 构建工具"。系统会自动检测 ARM64 架构并构建原生 ARM64 二进制文件。 + - 对于 Ubuntu 用户,在开始之前,建议运行以下命令以安装所需的依赖项: ```bash @@ -188,12 +190,35 @@ pake [url] [options] #### [targets] -选择输出的包格式,支持 `deb`、`appimage`、`rpm`,此选项仅适用于 Linux,默认为 `deb`。 +指定构建目标架构或格式: + +- **Linux**: `deb`, `appimage`, `deb-arm64`, `appimage-arm64`(默认:`deb`) +- **Windows**: `x64`, `arm64`(未指定时自动检测) +- **macOS**: `intel`, `apple`, `universal`(未指定时自动检测) ```shell ---targets +--targets + +# 示例: +--targets arm64 # Windows ARM64 +--targets x64 # Windows x64 +--targets universal # macOS 通用版本(Intel + Apple Silicon) +--targets apple # 仅 macOS Apple Silicon +--targets intel # 仅 macOS Intel +--targets deb # Linux DEB 包(x64) +--targets rpm # Linux RPM 包(x64) +--targets appimage # Linux AppImage(x64) +--targets deb-arm64 # Linux DEB 包(ARM64) +--targets rpm-arm64 # Linux RPM 包(ARM64) +--targets appimage-arm64 # Linux AppImage(ARM64) ``` +**Linux ARM64 注意事项**: + +- 交叉编译需要额外设置。需要安装 `gcc-aarch64-linux-gnu` 并配置交叉编译环境变量。 +- ARM64 支持让 Pake 应用可以在基于 ARM 的 Linux 设备上运行,包括 Linux 手机(postmarketOS、Ubuntu Touch)、树莓派和其他 ARM64 Linux 系统。 +- 使用 `--target appimage-arm64` 可以创建便携式 ARM64 应用,在不同的 ARM64 Linux 发行版上运行。 + #### [user-agent] 自定义浏览器的用户代理请求头,默认为空。 diff --git a/bin/builders/LinuxBuilder.ts b/bin/builders/LinuxBuilder.ts index 62e3525..a620ea6 100644 --- a/bin/builders/LinuxBuilder.ts +++ b/bin/builders/LinuxBuilder.ts @@ -1,19 +1,43 @@ +import path from 'path'; import BaseBuilder from './BaseBuilder'; import { PakeAppOptions } from '@/types'; import tauriConfig from '@/helpers/tauriConfig'; export default class LinuxBuilder extends BaseBuilder { + private buildFormat: string; + private buildArch: string; + constructor(options: PakeAppOptions) { super(options); + + // Parse target format and architecture + const target = options.targets || 'deb'; + if (target.includes('-arm64')) { + this.buildFormat = target.replace('-arm64', ''); + this.buildArch = 'arm64'; + } else { + this.buildFormat = target; + this.buildArch = 'auto'; + } + + // Set targets to format for Tauri + this.options.targets = this.buildFormat; } getFileName() { const { name, targets } = this.options; const version = tauriConfig.version; - let arch = process.arch === 'x64' ? 'amd64' : process.arch; - if (arch === 'arm64' && (targets === 'rpm' || targets === 'appimage')) { - arch = 'aarch64'; + // Determine architecture based on explicit target or auto-detect + let arch: string; + if (this.buildArch === 'arm64') { + arch = targets === 'rpm' || targets === 'appimage' ? 'aarch64' : 'arm64'; + } else { + // Auto-detect or default to current architecture + arch = process.arch === 'x64' ? 'amd64' : process.arch; + if (arch === 'arm64' && (targets === 'rpm' || targets === 'appimage')) { + arch = 'aarch64'; + } } // The RPM format uses different separators and version number formats @@ -34,6 +58,39 @@ export default class LinuxBuilder extends BaseBuilder { } } + protected getBuildCommand(): string { + const baseCommand = this.options.debug + ? 'npm run build:debug' + : 'npm run build'; + + // Use temporary config directory to avoid modifying source files + const configPath = path.join('src-tauri', '.pake', 'tauri.conf.json'); + let fullCommand = `${baseCommand} -- -c "${configPath}"`; + + // Add ARM64 target if explicitly specified + if (this.buildArch === 'arm64') { + fullCommand += ' --target aarch64-unknown-linux-gnu'; + } + + // Add features + const features = ['cli-build']; + if (features.length > 0) { + fullCommand += ` --features ${features.join(',')}`; + } + + return fullCommand; + } + + protected getBasePath(): string { + const basePath = this.options.debug ? 'debug' : 'release'; + + if (this.buildArch === 'arm64') { + return `src-tauri/target/aarch64-unknown-linux-gnu/${basePath}/bundle/`; + } + + return super.getBasePath(); + } + protected getFileType(target: string): string { if (target === 'appimage') { return 'AppImage'; diff --git a/bin/builders/MacBuilder.ts b/bin/builders/MacBuilder.ts index ed0703e..eee29d2 100644 --- a/bin/builders/MacBuilder.ts +++ b/bin/builders/MacBuilder.ts @@ -4,37 +4,56 @@ import { PakeAppOptions } from '@/types'; import BaseBuilder from './BaseBuilder'; export default class MacBuilder extends BaseBuilder { + private buildFormat: string; + private buildArch: string; + constructor(options: PakeAppOptions) { super(options); + + // Store the original targets value for architecture selection + this.buildArch = options.targets || 'auto'; + // Use DMG by default for distribution // Only create app bundles for testing to avoid user interaction if (process.env.PAKE_CREATE_APP === '1') { - this.options.targets = 'app'; + this.buildFormat = 'app'; } else { - this.options.targets = 'dmg'; + this.buildFormat = 'dmg'; } + + // Set targets to format for Tauri + this.options.targets = this.buildFormat; } getFileName(): string { const { name } = this.options; // For app bundles, use simple name without version/arch - if (this.options.targets === 'app') { + if (this.buildFormat === 'app') { return name; } // For DMG files, use versioned filename let arch: string; - if (this.options.multiArch) { + if (this.buildArch === 'universal' || this.options.multiArch) { arch = 'universal'; + } else if (this.buildArch === 'apple') { + arch = 'aarch64'; + } else if (this.buildArch === 'intel') { + arch = 'x64'; } else { + // Auto-detect based on current architecture arch = process.arch === 'arm64' ? 'aarch64' : process.arch; } return `${name}_${tauriConfig.version}_${arch}`; } protected getBuildCommand(): string { - if (this.options.multiArch) { + // Determine if we need universal build + const needsUniversal = + this.buildArch === 'universal' || this.options.multiArch; + + if (needsUniversal) { const baseCommand = this.options.debug ? 'npm run tauri build -- --debug' : 'npm run tauri build --'; @@ -56,14 +75,63 @@ export default class MacBuilder extends BaseBuilder { fullCommand += ` --features ${features.join(',')}`; } + return fullCommand; + } else if (this.buildArch === 'apple') { + // Build for Apple Silicon only + const baseCommand = this.options.debug + ? 'npm run tauri build -- --debug' + : 'npm run tauri build --'; + const configPath = path.join('src-tauri', '.pake', 'tauri.conf.json'); + let fullCommand = `${baseCommand} --target aarch64-apple-darwin -c "${configPath}"`; + + // Add features + const features = ['cli-build']; + const macOSVersion = this.getMacOSMajorVersion(); + if (macOSVersion >= 23) { + features.push('macos-proxy'); + } + if (features.length > 0) { + fullCommand += ` --features ${features.join(',')}`; + } + + return fullCommand; + } else if (this.buildArch === 'intel') { + // Build for Intel only + const baseCommand = this.options.debug + ? 'npm run tauri build -- --debug' + : 'npm run tauri build --'; + const configPath = path.join('src-tauri', '.pake', 'tauri.conf.json'); + let fullCommand = `${baseCommand} --target x86_64-apple-darwin -c "${configPath}"`; + + // Add features + const features = ['cli-build']; + const macOSVersion = this.getMacOSMajorVersion(); + if (macOSVersion >= 23) { + features.push('macos-proxy'); + } + if (features.length > 0) { + fullCommand += ` --features ${features.join(',')}`; + } + return fullCommand; } + return super.getBuildCommand(); } protected getBasePath(): string { - return this.options.multiArch - ? 'src-tauri/target/universal-apple-darwin/release/bundle' - : super.getBasePath(); + const needsUniversal = + this.buildArch === 'universal' || this.options.multiArch; + const basePath = this.options.debug ? 'debug' : 'release'; + + if (needsUniversal) { + return `src-tauri/target/universal-apple-darwin/${basePath}/bundle`; + } else if (this.buildArch === 'apple') { + return `src-tauri/target/aarch64-apple-darwin/${basePath}/bundle`; + } else if (this.buildArch === 'intel') { + return `src-tauri/target/x86_64-apple-darwin/${basePath}/bundle`; + } + + return super.getBasePath(); } } diff --git a/bin/builders/WinBuilder.ts b/bin/builders/WinBuilder.ts index 077032a..42e834c 100644 --- a/bin/builders/WinBuilder.ts +++ b/bin/builders/WinBuilder.ts @@ -1,17 +1,93 @@ +import path from 'path'; import BaseBuilder from './BaseBuilder'; import { PakeAppOptions } from '@/types'; import tauriConfig from '@/helpers/tauriConfig'; export default class WinBuilder extends BaseBuilder { + private buildFormat: string = 'msi'; + private buildArch: string; + constructor(options: PakeAppOptions) { super(options); - this.options.targets = 'msi'; + // Store the original targets value for architecture selection + this.buildArch = options.targets || 'auto'; + // Set targets to msi format for Tauri + this.options.targets = this.buildFormat; } getFileName(): string { const { name } = this.options; - const { arch } = process; const language = tauriConfig.bundle.windows.wix.language[0]; - return `${name}_${tauriConfig.version}_${arch}_${language}`; + + // Determine architecture name based on explicit targets option or auto-detect + let targetArch: string; + if (this.buildArch === 'arm64') { + targetArch = 'aarch64'; + } else if (this.buildArch === 'x64') { + targetArch = 'x64'; + } else { + // Auto-detect based on current architecture if no explicit target + const archMap: { [key: string]: string } = { + x64: 'x64', + arm64: 'aarch64', + }; + targetArch = archMap[process.arch] || process.arch; + } + + return `${name}_${tauriConfig.version}_${targetArch}_${language}`; + } + + protected getBuildCommand(): string { + const baseCommand = this.options.debug + ? 'npm run build:debug' + : 'npm run build'; + + // Use temporary config directory to avoid modifying source files + const configPath = path.join('src-tauri', '.pake', 'tauri.conf.json'); + let fullCommand = `${baseCommand} -- -c "${configPath}"`; + + // Determine build target based on explicit targets option or auto-detect + let buildTarget: string; + if (this.buildArch === 'arm64') { + buildTarget = 'aarch64-pc-windows-msvc'; + } else if (this.buildArch === 'x64') { + buildTarget = 'x86_64-pc-windows-msvc'; + } else { + // Auto-detect based on current architecture if no explicit target + buildTarget = + process.arch === 'arm64' + ? 'aarch64-pc-windows-msvc' + : 'x86_64-pc-windows-msvc'; + } + + fullCommand += ` --target ${buildTarget}`; + + // Add features + const features = ['cli-build']; + if (features.length > 0) { + fullCommand += ` --features ${features.join(',')}`; + } + + return fullCommand; + } + + protected getBasePath(): string { + const basePath = this.options.debug ? 'debug' : 'release'; + + // Determine target based on explicit targets option or auto-detect + let target: string; + if (this.buildArch === 'arm64') { + target = 'aarch64-pc-windows-msvc'; + } else if (this.buildArch === 'x64') { + target = 'x86_64-pc-windows-msvc'; + } else { + // Auto-detect based on current architecture if no explicit target + target = + process.arch === 'arm64' + ? 'aarch64-pc-windows-msvc' + : 'x86_64-pc-windows-msvc'; + } + + return `src-tauri/target/${target}/${basePath}/bundle/`; } } diff --git a/bin/cli.ts b/bin/cli.ts index 23188ab..d71a5c0 100644 --- a/bin/cli.ts +++ b/bin/cli.ts @@ -81,9 +81,10 @@ program .hideHelp(), ) .addOption( - new Option('--targets ', 'For Linux, option "deb" or "appimage"') - .default(DEFAULT.targets) - .hideHelp(), + new Option( + '--targets ', + 'Build target: Linux: "deb", "rpm", "appimage", "deb-arm64", "rpm-arm64", "appimage-arm64"; Windows: "x64", "arm64"; macOS: "intel", "apple", "universal"', + ).default(DEFAULT.targets), ) .addOption( new Option( diff --git a/bin/helpers/merge.ts b/bin/helpers/merge.ts index 147781e..8cec845 100644 --- a/bin/helpers/merge.ts +++ b/bin/helpers/merge.ts @@ -172,9 +172,20 @@ StartupNotify=true [`/usr/share/applications/${desktopFileName}`]: `assets/${desktopFileName}`, }; - const validTargets = ['deb', 'appimage', 'rpm']; + const validTargets = [ + 'deb', + 'appimage', + 'rpm', + 'deb-arm64', + 'appimage-arm64', + 'rpm-arm64', + ]; + const baseTarget = options.targets.includes('-arm64') + ? options.targets.replace('-arm64', '') + : options.targets; + if (validTargets.includes(options.targets)) { - tauriConf.bundle.targets = [options.targets]; + tauriConf.bundle.targets = [baseTarget]; } else { logger.warn( `✼ The target must be one of ${validTargets.join(', ')}, the default 'deb' will be used.`, diff --git a/bin/types.ts b/bin/types.ts index 0cc4e8b..ee739b4 100644 --- a/bin/types.ts +++ b/bin/types.ts @@ -57,7 +57,8 @@ export interface PakeCliOptions { // Multi arch, supports both Intel and M1 chips, only for Mac multiArch: boolean; - // Package output, valid for Linux users, default is deb, optional appimage, or all (i.e., output both deb and all); + // Build target architecture/format: + // Linux: "deb", "appimage", "deb-arm64", "appimage-arm64"; Windows: "x64", "arm64"; macOS: "intel", "apple", "universal" targets: string; // Debug mode, outputs more logs