From 5f9cbf9a83e59bacfdfc0c2a328ae44aa980a096 Mon Sep 17 00:00:00 2001 From: Nogringo Date: Mon, 28 Sep 2026 19:46:44 +0200 Subject: [PATCH] fix(appimage): launch the CMake binary instead of the pubspec name The AppRun script and the desktop entry Exec lines used the pubspec name, while Flutter builds the executable under BINARY_NAME from linux/CMakeLists.txt. When the two differ, the AppImage fails at launch with "No such file or directory". Dart: MakeAppImageConfig now extends MakeLinuxPackageConfig, like the deb and rpm configs. The metainfo file keeps the pubspec name so it still matches the .desktop file. Rust: use app_binary_name, which the CLI already resolves from CMake. --- crates/app_packager/src/linux/appimage.rs | 37 ++++++++++++++++--- .../appimage/app_package_maker_appimage.dart | 2 +- .../makers/appimage/make_appimage_config.dart | 8 ++-- .../src/makers/linux_desktop_entry_test.dart | 28 ++++++++++++++ 4 files changed, 65 insertions(+), 10 deletions(-) diff --git a/crates/app_packager/src/linux/appimage.rs b/crates/app_packager/src/linux/appimage.rs index a9469bd6..045ba751 100644 --- a/crates/app_packager/src/linux/appimage.rs +++ b/crates/app_packager/src/linux/appimage.rs @@ -60,6 +60,7 @@ impl AppImageMakeConfig { /// (including `[Desktop Action]` sections). fn desktop_file(&self, config: &PackageConfig) -> String { let app_name = &config.app_name; + let binary_name = &config.app_binary_name; let mut fields: Vec<(&str, String)> = vec![ ( "Name", @@ -73,7 +74,10 @@ impl AppImageMakeConfig { .clone() .unwrap_or_else(|| "A Flutter Application".to_string()), ), - ("Exec", format!("LD_LIBRARY_PATH=usr/lib {} %u", app_name)), + ( + "Exec", + format!("LD_LIBRARY_PATH=usr/lib {} %u", binary_name), + ), ("Icon", app_name.clone()), ("Type", "Application".to_string()), ( @@ -115,7 +119,7 @@ impl AppImageMakeConfig { "[Desktop Action {}]\nName={}\nExec=LD_LIBRARY_PATH=usr/lib {} {} %u", action.label, action.name, - app_name, + binary_name, action.arguments.join(" "), ) }) @@ -129,7 +133,7 @@ impl AppImageMakeConfig { fn app_run(&self, config: &PackageConfig) -> String { format!( "#!/bin/bash\n\ncd \"$(dirname \"$0\")\"\nexport LD_LIBRARY_PATH=usr/lib\nexec ./{}\n", - config.app_name + config.app_binary_name ) } } @@ -276,8 +280,7 @@ impl AppPackager for LinuxAppImagePackager { }; let metainfo_dir = app_dir.join("usr/share/metainfo"); std::fs::create_dir_all(&metainfo_dir)?; - // Dart's AppImage config extends `MakeConfig` (not the Linux - // variant), so its `appBinaryName` is the pubspec name. + // Named after the pubspec to match the `.desktop` file, as in Dart. std::fs::copy( metainfo_path, metainfo_dir.join(format!("{}{}", config.app_name, ext)), @@ -423,6 +426,30 @@ actions: assert!(script.contains("exec ./hola_amigos")); } + #[test] + fn launches_binary_name_when_it_differs_from_app_name() { + let config = PackageConfig { + app_binary_name: "hola".into(), + ..test_config() + }; + let mc: AppImageMakeConfig = serde_yaml::from_str( + r#" +actions: + - label: Gallery + name: Open Gallery + arguments: + - --gallery +"#, + ) + .unwrap(); + + assert!(mc.app_run(&config).contains("exec ./hola\n")); + let desktop = mc.desktop_file(&config); + assert!(desktop.contains("Exec=LD_LIBRARY_PATH=usr/lib hola %u")); + assert!(desktop.contains("Exec=LD_LIBRARY_PATH=usr/lib hola --gallery %u")); + assert!(desktop.contains("Icon=hola_amigos")); + } + #[test] fn ldd_output_parsing() { let output = "\tlinux-vdso.so.1 (0x00007ffd)\n\tlibkeybinder-3.0.so.0 => /lib64/libkeybinder-3.0.so.0 (0x00007f65)\n\tlibc.so.6 => /lib64/libc.so.6 (0x00007f64)\n\t/lib64/ld-linux-x86-64.so.2 (0x00007f66)\n"; diff --git a/packages/flutter_app_packager/lib/src/makers/appimage/app_package_maker_appimage.dart b/packages/flutter_app_packager/lib/src/makers/appimage/app_package_maker_appimage.dart index fe5db23b..07ec848a 100644 --- a/packages/flutter_app_packager/lib/src/makers/appimage/app_package_maker_appimage.dart +++ b/packages/flutter_app_packager/lib/src/makers/appimage/app_package_maker_appimage.dart @@ -171,7 +171,7 @@ class AppPackageMakerAppImage extends AppPackageMaker { await metainfoFile.copy( path.join( metainfoDir, - makeConfig.appBinaryName + path.extension(makeConfig.metainfo!, 2), + makeConfig.appName + path.extension(makeConfig.metainfo!, 2), ), ); } diff --git a/packages/flutter_app_packager/lib/src/makers/appimage/make_appimage_config.dart b/packages/flutter_app_packager/lib/src/makers/appimage/make_appimage_config.dart index 1f254786..c4a38ece 100644 --- a/packages/flutter_app_packager/lib/src/makers/appimage/make_appimage_config.dart +++ b/packages/flutter_app_packager/lib/src/makers/appimage/make_appimage_config.dart @@ -30,7 +30,7 @@ class AppImageAction { } } -class MakeAppImageConfig extends MakeConfig { +class MakeAppImageConfig extends MakeLinuxPackageConfig { MakeAppImageConfig({ required this.displayName, required this.icon, @@ -81,7 +81,7 @@ class MakeAppImageConfig extends MakeConfig { final fields = { 'Name': displayName, 'GenericName': genericName, - 'Exec': 'LD_LIBRARY_PATH=usr/lib $appName %u', + 'Exec': 'LD_LIBRARY_PATH=usr/lib $appBinaryName %u', 'Icon': appName, 'Type': 'Application', 'StartupNotify': startupNotify ? 'true' : 'false', @@ -97,7 +97,7 @@ class MakeAppImageConfig extends MakeConfig { final fields = { 'Name': action.name, 'Exec': - 'LD_LIBRARY_PATH=usr/lib $appName ${action.arguments.join(' ')} %u', + 'LD_LIBRARY_PATH=usr/lib $appBinaryName ${action.arguments.join(' ')} %u', }; return '[Desktop Action ${action.label}]\n${fields.entries.map((e) => '${e.key}=${e.value}').join('\n')}'; }).join('\n\n'); @@ -111,7 +111,7 @@ class MakeAppImageConfig extends MakeConfig { cd "\$(dirname "\$0")" export LD_LIBRARY_PATH=usr/lib -exec ./$appName +exec ./$appBinaryName '''; } } diff --git a/packages/flutter_app_packager/test/src/makers/linux_desktop_entry_test.dart b/packages/flutter_app_packager/test/src/makers/linux_desktop_entry_test.dart index d87def40..1f0a8987 100644 --- a/packages/flutter_app_packager/test/src/makers/linux_desktop_entry_test.dart +++ b/packages/flutter_app_packager/test/src/makers/linux_desktop_entry_test.dart @@ -1,5 +1,6 @@ import 'dart:io'; +import 'package:flutter_app_packager/src/makers/appimage/make_appimage_config.dart'; import 'package:flutter_app_packager/src/makers/deb/make_deb_config.dart'; import 'package:flutter_app_packager/src/makers/pacman/make_pacman_config.dart'; import 'package:flutter_app_packager/src/makers/rpm/make_rpm_config.dart'; @@ -123,6 +124,33 @@ void main() { ); }); + test('appimage launches the CMake binary, not the pubspec name', () { + File('${tempDir.path}/linux/CMakeLists.txt') + .writeAsStringSync('set(BINARY_NAME "test_binary")\n'); + final config = MakeAppImageConfig.fromJson({ + 'display_name': 'Test App', + 'icon': 'icon.png', + 'actions': [ + { + 'label': 'NewWindow', + 'name': 'New Window', + 'arguments': ['--new-window'], + }, + ], + })..pubspec = _pubspec(); + + expect(config.appRunContent, contains('exec ./test_binary\n')); + expect( + config.desktopFileContent, + contains('Exec=LD_LIBRARY_PATH=usr/lib test_binary %u'), + ); + expect( + config.desktopFileContent, + contains('Exec=LD_LIBRARY_PATH=usr/lib test_binary --new-window %u'), + ); + expect(config.desktopFileContent, contains('Icon=test_app')); + }); + test('pacman keeps package version out of the desktop entry', () { final config = MakePacmanConfig( displayName: 'Test App',