Continue to optimize packaging issues under Linux

This commit is contained in:
Tw93
2025-10-20 10:15:51 +08:00
parent d4bbea917e
commit eb128c6aa7
7 changed files with 165 additions and 50 deletions

View File

@@ -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<string, string> = {
...(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,

20
bin/utils/shell.ts vendored
View File

@@ -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);

62
dist/cli.js vendored
View File

@@ -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 <url> --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;

View File

@@ -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 \
<arguments>
# 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
```

View File

@@ -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 CLIAppImage 构建需要 FUSE 权限)
docker run --rm --privileged \
--device /dev/fuse \
--security-opt apparmor=unconfined \
-v YOUR_DIR:/output \
ghcr.io/tw93/pake \
<arguments>
# 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
```

View File

@@ -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.
---

View File

@@ -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 社区推荐的官方解决方法。
---