2 #include "includes/util.h"
3 #include "includes/log.h"
4 #include "includes/appdir_root_setup.h"
7 namespace bf = boost::filesystem;
9 namespace linuxdeploy {
10 using namespace desktopfile;
13 using namespace appdir;
17 class AppDirRootSetup::Private {
19 static constexpr auto APPRUN_HOOKS_DIRNAME = "apprun-hooks";
25 explicit Private(const AppDir& appDir) : appDir(appDir) {}
28 static void makeFileExecutable(const bf::path& path) {
30 bf::perms::owner_all | bf::perms::group_read | bf::perms::others_read |
31 bf::perms::group_exe | bf::perms::others_exe
36 bool deployDesktopFileAndIcon(const DesktopFile& desktopFile) const {
37 ldLog() << "Deploying desktop file to AppDir root:" << desktopFile.path() << std::endl;
39 // copy desktop file to root directory
40 if (!appDir.createRelativeSymlink(desktopFile.path(), appDir.path())) {
41 ldLog() << LD_ERROR << "Failed to create link to desktop file in AppDir root:" << desktopFile.path() << std::endl;
45 // look for suitable icon
46 DesktopFileEntry iconEntry;
48 if (!desktopFile.getEntry("Desktop Entry", "Icon", iconEntry)) {
49 ldLog() << LD_ERROR << "Icon entry missing in desktop file:" << desktopFile.path() << std::endl;
53 bool iconDeployed = false;
55 const auto foundIconPaths = appDir.deployedIconPaths();
57 if (foundIconPaths.empty()) {
58 ldLog() << LD_ERROR << "Could not find icon executable for Icon entry:" << iconEntry.value() << std::endl;
62 for (const auto& iconPath : foundIconPaths) {
63 ldLog() << LD_DEBUG << "Icon found:" << iconPath << std::endl;
65 const bool matchesFilenameWithExtension = iconPath.filename() == iconEntry.value();
67 if (iconPath.stem() == iconEntry.value() || matchesFilenameWithExtension) {
68 if (matchesFilenameWithExtension) {
69 ldLog() << LD_WARNING << "Icon= entry filename contains extension" << std::endl;
72 ldLog() << "Deploying icon to AppDir root:" << iconPath << std::endl;
74 if (!appDir.createRelativeSymlink(iconPath, appDir.path())) {
75 ldLog() << LD_ERROR << "Failed to create symlink for icon in AppDir root:" << iconPath << std::endl;
85 ldLog() << LD_ERROR << "Could not find suitable icon for Icon entry:" << iconEntry.value() << std::endl;
92 bool deployCustomAppRunFile(const bf::path& customAppRunPath) const {
93 // copy custom AppRun executable
94 ldLog() << "Deploying custom AppRun:" << customAppRunPath << std::endl;
96 const auto appRunPath = appDir.path() / "AppRun";
98 if (!appDir.copyFile(customAppRunPath, appRunPath))
101 ldLog() << "Making AppRun file executable: " << appRunPath << std::endl;
102 makeFileExecutable(appRunPath);
107 bool deployStandardAppRunFromDesktopFile(const DesktopFile& desktopFile, const bf::path& customAppRunPath) const {
108 // check if there is a custom AppRun already
109 // in that case, skip deployment of symlink
110 if (bf::exists(appDir.path() / "AppRun")) {
111 ldLog() << LD_WARNING << "Existing AppRun detected, skipping deployment of symlink" << std::endl;
113 // look for suitable binary to create AppRun symlink
114 DesktopFileEntry executableEntry;
116 if (!desktopFile.getEntry("Desktop Entry", "Exec", executableEntry)) {
117 ldLog() << LD_ERROR << "Exec entry missing in desktop file:" << desktopFile.path()
122 auto executableName = util::split(executableEntry.value())[0];
124 const auto foundExecutablePaths = appDir.deployedExecutablePaths();
126 if (foundExecutablePaths.empty()) {
127 ldLog() << LD_ERROR << "Could not find suitable executable for Exec entry:" << executableName
132 bool deployedExecutable = false;
134 for (const auto& executablePath : foundExecutablePaths) {
135 ldLog() << LD_DEBUG << "Executable found:" << executablePath << std::endl;
137 if (executablePath.filename() == executableName) {
138 ldLog() << "Deploying AppRun symlink for executable in AppDir root:" << executablePath
141 if (!appDir.createRelativeSymlink(executablePath, appDir.path() / "AppRun")) {
143 << "Failed to create AppRun symlink for executable in AppDir root:"
144 << executablePath << std::endl;
148 deployedExecutable = true;
153 if (!deployedExecutable) {
154 ldLog() << LD_ERROR << "Could not deploy symlink for executable: could not find suitable executable for Exec entry:" << executableName << std::endl;
162 bool deployAppRunWrapperIfNecessary() const {
163 const bf::path appRunPath(appDir.path() / "AppRun");
164 const bf::path wrappedAppRunPath(appRunPath.string() + ".wrapped");
166 const bf::path appRunHooksPath(appDir.path() / APPRUN_HOOKS_DIRNAME);
168 // first, we check whether there's that special directory containing hooks
169 if (!bf::is_directory(appRunHooksPath)) {
170 ldLog() << LD_DEBUG << "Could not find apprun-hooks dir, no need to deploy the AppRun wrapper" << std::endl;
174 // if there's no files in there we don't have to do anything
175 bf::directory_iterator firstRegularFile = std::find_if(
176 bf::directory_iterator(appRunHooksPath),
177 bf::directory_iterator{},
178 [](const bf::path& p) {
179 return bf::is_regular_file(p);
182 if (firstRegularFile == bf::directory_iterator{}) {
183 ldLog() << LD_WARNING << "Found an empty apprun-hooks directory, assuming there is no need to deploy the AppRun wrapper" << std::endl;
187 // any file within that directory is considered to be a script
188 // we can't perform any validity checks, that would be way too much complexity and even tools which
189 // claim they can, like e.g., shellcheck, aren't perfect, they only aid in avoiding bugs but cannot
190 // prevent them completely
192 // let's put together the wrapper script's contents
193 std::ostringstream oss;
195 oss << "#! /usr/bin/env bash" << std::endl
197 << "# autogenerated by makeappimage" << std::endl
199 << "# make sure errors in sourced scripts will cause this script to stop" << std::endl
200 << "set -e" << std::endl
202 << "this_dir=\"$(readlink -f \"$(dirname \"$0\")\")\"" << std::endl
205 std::for_each(bf::directory_iterator(appRunHooksPath), bf::directory_iterator{}, [&oss](const bf::path& p) {
206 if (!bf::is_regular_file(p))
209 oss << "source \"$this_dir\"/" << APPRUN_HOOKS_DIRNAME << "/" << p.filename() << std::endl;
213 << "exec \"$this_dir\"/AppRun.wrapped \"$@\"" << std::endl;
215 // first we need to make sure we're not running this more than once
216 // this might cause more harm than good
217 // we require the user to clean up the mess at first
218 // FIXME: try to find a way how to rewrap AppRun on subsequent runs or, even better, become idempotent
219 if (bf::exists(wrappedAppRunPath)) {
220 ldLog() << LD_WARNING << "Already found wrapped AppRun, using existing file/symlink" << std::endl;
222 // backup original AppRun
223 bf::rename(appRunPath, wrappedAppRunPath);
226 // in case the above check triggered a warning, it's possible that there is another AppRun in the AppDir
227 // this one has to be cleaned up in that case
228 if (bf::exists(appRunPath)) {
229 ldLog() << LD_WARNING << "Found an AppRun file/symlink, possibly due to re-run of makeappimage, "
230 "overwriting" << std::endl;
231 bf::remove(appRunPath);
235 // install new script
236 std::ofstream ofs(appRunPath.string());
239 // make sure data is written to disk
243 // make new file executable
244 makeFileExecutable(appRunPath);
251 AppDirRootSetup::AppDirRootSetup(const AppDir& appDir) : d(new Private(appDir)) {}
253 bool AppDirRootSetup::run(const DesktopFile& desktopFile, const bf::path& customAppRunPath) const {
254 // first step that is always required is to deploy the desktop file and the corresponding icon
255 if (!d->deployDesktopFileAndIcon(desktopFile)) {
256 ldLog() << LD_DEBUG << "deployDesktopFileAndIcon returned false" << std::endl;
260 // the algorithm depends on whether the user wishes to deploy their own AppRun file
261 // in case they do, the algorithm shall deploy that file
262 // otherwise, the standard algorithm shall be run which takes information from the desktop file to
263 // deploy a symlink pointing to the AppImage's main binary
264 // this allows power users to define their own AppImage initialization steps or run different binaries
265 // based on parameters etc.
266 if (!customAppRunPath.empty()) {
267 if (!d->deployCustomAppRunFile(customAppRunPath)) {
268 ldLog() << LD_DEBUG << "deployCustomAppRunFile returned false" << std::endl;
272 if (!d->deployStandardAppRunFromDesktopFile(desktopFile, customAppRunPath)) {
273 ldLog() << LD_DEBUG << "deployStandardAppRunFromDesktopFile returned false" << std::endl;
278 // TODO: This code below can probably go, no plugin support anymore.
279 // plugins might need to run some initializing code to make certain features work
280 // these involve setting environment variables because libraries or frameworks don't support any other
281 // way of pointing them to resources inside the AppDir instead of looking into config files in locations
282 // inside the AppImage, etc.
283 // the makeappimage plugin specification states that if plugins put files into a specified directory in
284 // the AppImage, linuxdeploy will make sure they're run before running the regular AppRun
285 if (!d->deployAppRunWrapperIfNecessary()) {
286 ldLog() << LD_DEBUG << "deployAppRunWrapperIfNecessary returned false" << std::endl;