From eb128c6aa7f34a9b3a5584cbed4c598e57b17f57 Mon Sep 17 00:00:00 2001 From: Tw93 Date: Mon, 20 Oct 2025 10:15:51 +0800 Subject: [PATCH] Continue to optimize packaging issues under Linux --- bin/builders/BaseBuilder.ts | 55 +++++++++++++++++++++++++++----- bin/utils/shell.ts | 20 ++++++++++-- dist/cli.js | 62 ++++++++++++++++++++++++++++++++----- docs/cli-usage.md | 15 +++++---- docs/cli-usage_CN.md | 17 +++++----- docs/faq.md | 23 ++++++++------ docs/faq_CN.md | 23 ++++++++------ 7 files changed, 165 insertions(+), 50 deletions(-) diff --git a/bin/builders/BaseBuilder.ts b/bin/builders/BaseBuilder.ts index 0d71fc0..18d4d5e 100644 --- a/bin/builders/BaseBuilder.ts +++ b/bin/builders/BaseBuilder.ts @@ -162,11 +162,15 @@ export default abstract class BaseBuilder { // Show static message to keep the status visible logger.warn('✸ Building app...'); - const buildEnv = { - ...this.getBuildEnvironment(), - ...(process.env.NO_STRIP && { NO_STRIP: process.env.NO_STRIP }), + const baseEnv = this.getBuildEnvironment(); + let buildEnv: Record = { + ...(baseEnv ?? {}), + ...(process.env.NO_STRIP ? { NO_STRIP: process.env.NO_STRIP } : {}), }; + const resolveExecEnv = () => + Object.keys(buildEnv).length > 0 ? buildEnv : undefined; + // Warn users about potential AppImage build failures on modern Linux systems. // The linuxdeploy tool bundled in Tauri uses an older strip tool that doesn't // recognize the .relr.dyn section introduced in glibc 2.38+. @@ -181,11 +185,31 @@ export default abstract class BaseBuilder { } } - await shellExec( - `cd "${npmDirectory}" && ${this.getBuildCommand(packageManager)}`, - this.getBuildTimeout(), - buildEnv, - ); + const buildCommand = `cd "${npmDirectory}" && ${this.getBuildCommand(packageManager)}`; + const buildTimeout = this.getBuildTimeout(); + + try { + await shellExec(buildCommand, buildTimeout, resolveExecEnv()); + } catch (error) { + const shouldRetryWithoutStrip = + process.platform === 'linux' && + this.options.targets === 'appimage' && + !buildEnv.NO_STRIP && + this.isLinuxDeployStripError(error); + + if (shouldRetryWithoutStrip) { + logger.warn( + '⚠ AppImage build failed during linuxdeploy strip step, retrying with NO_STRIP=1 automatically.', + ); + buildEnv = { + ...buildEnv, + NO_STRIP: '1', + }; + await shellExec(buildCommand, buildTimeout, resolveExecEnv()); + } else { + throw error; + } + } // Copy app const fileName = this.getFileName(); @@ -216,6 +240,21 @@ export default abstract class BaseBuilder { abstract getFileName(): string; + private isLinuxDeployStripError(error: unknown): boolean { + if (!(error instanceof Error) || !error.message) { + return false; + } + const message = error.message.toLowerCase(); + return ( + message.includes('linuxdeploy') || + message.includes('failed to run linuxdeploy') || + message.includes('strip:') || + message.includes('unable to recognise the format of the input file') || + message.includes('appimage tool failed') || + message.includes('strip tool') + ); + } + // 架构映射配置 protected static readonly ARCH_MAPPINGS: Record< string, diff --git a/bin/utils/shell.ts b/bin/utils/shell.ts index 7c14980..e857d8f 100644 --- a/bin/utils/shell.ts +++ b/bin/utils/shell.ts @@ -31,11 +31,13 @@ export async function shellExec( // Provide helpful guidance for common Linux AppImage build failures // caused by strip tool incompatibility with modern glibc (2.38+) + const lowerError = errorMessage.toLowerCase(); + if ( process.platform === 'linux' && - (errorMessage.includes('linuxdeploy') || - errorMessage.includes('appimage') || - errorMessage.includes('strip')) + (lowerError.includes('linuxdeploy') || + lowerError.includes('appimage') || + lowerError.includes('strip')) ) { errorMsg += '\n\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n' + @@ -50,6 +52,18 @@ export async function shellExec( ' • Update binutils: sudo apt install binutils (or pacman -S binutils)\n' + ' • Detailed guide: https://github.com/tw93/Pake/blob/main/docs/faq.md\n' + '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'; + + if ( + lowerError.includes('fuse') || + lowerError.includes('operation not permitted') || + lowerError.includes('/dev/fuse') + ) { + errorMsg += + '\n\nDocker / Container hint:\n' + + ' AppImage tooling needs access to /dev/fuse. When running inside Docker, add:\n' + + ' --privileged --device /dev/fuse --security-opt apparmor=unconfined\n' + + ' or run on the host directly.'; + } } throw new Error(errorMsg); diff --git a/dist/cli.js b/dist/cli.js index 3be1727..4e023a8 100755 --- a/dist/cli.js +++ b/dist/cli.js @@ -223,10 +223,11 @@ async function shellExec(command, timeout = 300000, env) { let errorMsg = `Error occurred while executing command "${command}". Exit code: ${exitCode}. Details: ${errorMessage}`; // Provide helpful guidance for common Linux AppImage build failures // caused by strip tool incompatibility with modern glibc (2.38+) + const lowerError = errorMessage.toLowerCase(); if (process.platform === 'linux' && - (errorMessage.includes('linuxdeploy') || - errorMessage.includes('appimage') || - errorMessage.includes('strip'))) { + (lowerError.includes('linuxdeploy') || + lowerError.includes('appimage') || + lowerError.includes('strip'))) { errorMsg += '\n\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n' + 'Linux AppImage Build Failed\n' + @@ -240,6 +241,15 @@ async function shellExec(command, timeout = 300000, env) { ' • Update binutils: sudo apt install binutils (or pacman -S binutils)\n' + ' • Detailed guide: https://github.com/tw93/Pake/blob/main/docs/faq.md\n' + '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'; + if (lowerError.includes('fuse') || + lowerError.includes('operation not permitted') || + lowerError.includes('/dev/fuse')) { + errorMsg += + '\n\nDocker / Container hint:\n' + + ' AppImage tooling needs access to /dev/fuse. When running inside Docker, add:\n' + + ' --privileged --device /dev/fuse --security-opt apparmor=unconfined\n' + + ' or run on the host directly.'; + } } throw new Error(errorMsg); } @@ -816,10 +826,12 @@ class BaseBuilder { buildSpinner.stop(); // Show static message to keep the status visible logger.warn('✸ Building app...'); - const buildEnv = { - ...this.getBuildEnvironment(), - ...(process.env.NO_STRIP && { NO_STRIP: process.env.NO_STRIP }), + const baseEnv = this.getBuildEnvironment(); + let buildEnv = { + ...(baseEnv ?? {}), + ...(process.env.NO_STRIP ? { NO_STRIP: process.env.NO_STRIP } : {}), }; + const resolveExecEnv = () => Object.keys(buildEnv).length > 0 ? buildEnv : undefined; // Warn users about potential AppImage build failures on modern Linux systems. // The linuxdeploy tool bundled in Tauri uses an older strip tool that doesn't // recognize the .relr.dyn section introduced in glibc 2.38+. @@ -829,7 +841,28 @@ class BaseBuilder { logger.warn('⚠ If build fails, retry with: NO_STRIP=1 pake --targets appimage'); } } - await shellExec(`cd "${npmDirectory}" && ${this.getBuildCommand(packageManager)}`, this.getBuildTimeout(), buildEnv); + const buildCommand = `cd "${npmDirectory}" && ${this.getBuildCommand(packageManager)}`; + const buildTimeout = this.getBuildTimeout(); + try { + await shellExec(buildCommand, buildTimeout, resolveExecEnv()); + } + catch (error) { + const shouldRetryWithoutStrip = process.platform === 'linux' && + this.options.targets === 'appimage' && + !buildEnv.NO_STRIP && + this.isLinuxDeployStripError(error); + if (shouldRetryWithoutStrip) { + logger.warn('⚠ AppImage build failed during linuxdeploy strip step, retrying with NO_STRIP=1 automatically.'); + buildEnv = { + ...buildEnv, + NO_STRIP: '1', + }; + await shellExec(buildCommand, buildTimeout, resolveExecEnv()); + } + else { + throw error; + } + } // Copy app const fileName = this.getFileName(); const fileType = this.getFileType(target); @@ -852,6 +885,18 @@ class BaseBuilder { getFileType(target) { return target; } + isLinuxDeployStripError(error) { + if (!(error instanceof Error) || !error.message) { + return false; + } + const message = error.message.toLowerCase(); + return (message.includes('linuxdeploy') || + message.includes('failed to run linuxdeploy') || + message.includes('strip:') || + message.includes('unable to recognise the format of the input file') || + message.includes('appimage tool failed') || + message.includes('strip tool')); + } /** * 解析目标架构 */ @@ -1209,7 +1254,8 @@ class LinuxBuilder extends BaseBuilder { // Enable verbose output for AppImage builds when debugging or PAKE_VERBOSE is set. // AppImage builds often fail with minimal error messages from linuxdeploy, // so verbose mode helps diagnose issues like strip failures and missing dependencies. - if (this.options.targets === 'appimage' && (this.options.debug || process.env.PAKE_VERBOSE)) { + if (this.options.targets === 'appimage' && + (this.options.debug || process.env.PAKE_VERBOSE)) { fullCommand += ' --verbose'; } return fullCommand; diff --git a/docs/cli-usage.md b/docs/cli-usage.md index 9e6efc8..7b540d2 100644 --- a/docs/cli-usage.md +++ b/docs/cli-usage.md @@ -443,16 +443,19 @@ After completing the above steps, your application should be successfully packag ## Docker ```shell -# On Linux, you can run the Pake CLI via Docker -docker run -it --rm \ # Run interactively, remove container after exit - -v YOUR_DIR:/output \ # Files from container's /output will be in YOU_DIR +# Run the Pake CLI via Docker (AppImage builds need FUSE access) +docker run --rm --privileged \ + --device /dev/fuse \ + --security-opt apparmor=unconfined \ + -v YOUR_DIR:/output \ ghcr.io/tw93/pake \ # For example: -docker run -it --rm \ +docker run --rm --privileged \ + --device /dev/fuse \ + --security-opt apparmor=unconfined \ -v ./packages:/output \ ghcr.io/tw93/pake \ - https://example.com --name myapp --icon ./icon.png - + https://example.com --name myapp --icon ./icon.png --targets appimage ``` diff --git a/docs/cli-usage_CN.md b/docs/cli-usage_CN.md index 82ada93..77cea4e 100644 --- a/docs/cli-usage_CN.md +++ b/docs/cli-usage_CN.md @@ -441,16 +441,19 @@ pake ./my-app/index.html --name "my-app" --use-local-file ## Docker 使用 ```shell -# 在Linux上,您可以通过 Docker 运行 Pake CLI。 -docker run -it --rm \ # Run interactively, remove container after exit - -v YOUR_DIR:/output \ # Files from container's /output will be in YOU_DIR +# 在 Linux 上通过 Docker 运行 Pake CLI(AppImage 构建需要 FUSE 权限) +docker run --rm --privileged \ + --device /dev/fuse \ + --security-opt apparmor=unconfined \ + -v YOUR_DIR:/output \ ghcr.io/tw93/pake \ -# For example: -docker run -it --rm \ +# 例如: +docker run --rm --privileged \ + --device /dev/fuse \ + --security-opt apparmor=unconfined \ -v ./packages:/output \ ghcr.io/tw93/pake \ - https://example.com --name MyApp --icon ./icon.png - + https://example.com --name MyApp --icon ./icon.png --targets appimage ``` diff --git a/docs/faq.md b/docs/faq.md index ce1cad1..2c8cfb2 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -16,12 +16,12 @@ Error: failed to run linuxdeploy Error: strip: Unable to recognise the format of the input file ``` -**Solution 1: Use NO_STRIP (Recommended)** +**Solution 1: Automatic NO_STRIP Retry (Recommended)** -Simply add `NO_STRIP=true` before your build command: +Pake CLI now automatically retries AppImage builds with `NO_STRIP=1` when linuxdeploy fails to strip the binary. To skip the strip step from the very first attempt (or when scripting your own builds), set the variable manually: ```bash -NO_STRIP=true pake https://example.com --name MyApp --targets appimage +NO_STRIP=1 pake https://example.com --name MyApp --targets appimage ``` This bypasses the library stripping process that often causes issues on certain Linux distributions. @@ -50,7 +50,7 @@ sudo apt install -y \ pkg-config ``` -Then try building again with `NO_STRIP=true`. +Then try building again (you can still pre-set `NO_STRIP=1` if you prefer). **Solution 3: Use DEB Format Instead** @@ -60,16 +60,21 @@ DEB packages are more stable on Debian-based systems: pake https://example.com --name MyApp --targets deb ``` -**Solution 4: Use Docker** +**Solution 4: Use Docker (with FUSE access)** -Build in a clean environment without installing dependencies: +Build in a clean environment without installing dependencies. AppImage tooling needs access to `/dev/fuse`, so run the container in privileged mode (or grant FUSE explicitly): ```bash -docker run --rm -v $(pwd)/output:/app/output \ +docker run --rm --privileged \ + --device /dev/fuse \ + --security-opt apparmor=unconfined \ + -v $(pwd)/output:/output \ ghcr.io/tw93/pake:latest \ - pake https://example.com --name MyApp --targets appimage + https://example.com --name MyApp --targets appimage ``` +> **Tip:** The generated AppImage may be owned by root. Run `sudo chown $(id -nu):$(id -ng) ./output/MyApp.AppImage` afterwards. + **Why This Happens:** This is a known issue with Tauri's linuxdeploy tool, which can fail when: @@ -78,7 +83,7 @@ This is a known issue with Tauri's linuxdeploy tool, which can fail when: - Building on newer distributions (Arch, Debian Trixie, etc.) - Missing WebKit2GTK or GTK development libraries -The `NO_STRIP=true` environment variable is the official workaround recommended by the Tauri community. +The `NO_STRIP=1` environment variable is the official workaround recommended by the Tauri community. --- diff --git a/docs/faq_CN.md b/docs/faq_CN.md index e5606c9..d4feabc 100644 --- a/docs/faq_CN.md +++ b/docs/faq_CN.md @@ -16,12 +16,12 @@ Error: failed to run linuxdeploy Error: strip: Unable to recognise the format of the input file ``` -**解决方案 1:使用 NO_STRIP(推荐)** +**解决方案 1:自动 NO_STRIP 重试(推荐)** -在构建命令前加上 `NO_STRIP=true`: +Pake CLI 已在 linuxdeploy 剥离失败时自动使用 `NO_STRIP=1` 进行二次构建。如果你希望一开始就跳过剥离步骤(或在脚本中使用),可以手动设置该变量: ```bash -NO_STRIP=true pake https://example.com --name MyApp --targets appimage +NO_STRIP=1 pake https://example.com --name MyApp --targets appimage ``` 这会绕过经常在某些 Linux 发行版上出现问题的库文件剥离过程。 @@ -50,7 +50,7 @@ sudo apt install -y \ pkg-config ``` -然后使用 `NO_STRIP=true` 再次尝试构建。 +然后再次尝试构建(也可以提前设置 `NO_STRIP=1`)。 **解决方案 3:改用 DEB 格式** @@ -60,16 +60,21 @@ DEB 包在基于 Debian 的系统上更稳定: pake https://example.com --name MyApp --targets deb ``` -**解决方案 4:使用 Docker** +**解决方案 4:使用 Docker(需开放 FUSE)** -在干净的环境中构建,无需安装依赖: +在干净的环境中构建,无需安装依赖。AppImage 工具需要访问 `/dev/fuse`,因此需要以特权模式运行(或显式授权 FUSE): ```bash -docker run --rm -v $(pwd)/output:/app/output \ +docker run --rm --privileged \ + --device /dev/fuse \ + --security-opt apparmor=unconfined \ + -v $(pwd)/output:/output \ ghcr.io/tw93/pake:latest \ - pake https://example.com --name MyApp --targets appimage + https://example.com --name MyApp --targets appimage ``` +> **提示:** 生成的 AppImage 可能属于 root,需要执行 `sudo chown $(id -nu):$(id -ng) ./output/MyApp.AppImage` 调整所有权。 + **原因:** 这是 Tauri 的 linuxdeploy 工具的已知问题,在以下情况下可能失败: @@ -78,7 +83,7 @@ docker run --rm -v $(pwd)/output:/app/output \ - 在较新的发行版上构建(Arch、Debian Trixie 等) - 缺少 WebKit2GTK 或 GTK 开发库 -`NO_STRIP=true` 环境变量是 Tauri 社区推荐的官方解决方法。 +`NO_STRIP=1` 环境变量是 Tauri 社区推荐的官方解决方法。 ---