123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457 |
- /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
- file LICENSE.rst or https://cmake.org/licensing for details. */
- #include "cmCPackAppImageGenerator.h"
- #include <algorithm>
- #include <cctype>
- #include <cstddef>
- #include <utility>
- #include <vector>
- #include <fcntl.h>
- #include "cmsys/FStream.hxx"
- #include "cmCPackLog.h"
- #include "cmELF.h"
- #include "cmGeneratedFileStream.h"
- #include "cmSystemTools.h"
- #include "cmValue.h"
- cmCPackAppImageGenerator::cmCPackAppImageGenerator() = default;
- cmCPackAppImageGenerator::~cmCPackAppImageGenerator() = default;
- int cmCPackAppImageGenerator::InitializeInternal()
- {
- this->SetOptionIfNotSet("CPACK_APPIMAGE_TOOL_EXECUTABLE", "appimagetool");
- this->AppimagetoolPath = cmSystemTools::FindProgram(
- *this->GetOption("CPACK_APPIMAGE_TOOL_EXECUTABLE"));
- if (this->AppimagetoolPath.empty()) {
- cmCPackLogger(
- cmCPackLog::LOG_ERROR,
- "Cannot find AppImageTool: '"
- << *this->GetOption("CPACK_APPIMAGE_TOOL_EXECUTABLE")
- << "' check if it's installed, is executable, or is in your PATH"
- << std::endl);
- return 0;
- }
- this->SetOptionIfNotSet("CPACK_APPIMAGE_PATCHELF_EXECUTABLE", "patchelf");
- this->PatchElfPath = cmSystemTools::FindProgram(
- *this->GetOption("CPACK_APPIMAGE_PATCHELF_EXECUTABLE"));
- if (this->PatchElfPath.empty()) {
- cmCPackLogger(
- cmCPackLog::LOG_ERROR,
- "Cannot find patchelf: '"
- << *this->GetOption("CPACK_APPIMAGE_PATCHELF_EXECUTABLE")
- << "' check if it's installed, is executable, or is in your PATH"
- << std::endl);
- return 0;
- }
- return Superclass::InitializeInternal();
- }
- int cmCPackAppImageGenerator::PackageFiles()
- {
- cmCPackLogger(cmCPackLog::LOG_OUTPUT,
- "AppDir: \"" << this->toplevel << "\"" << std::endl);
- // Desktop file must be in the toplevel dir
- auto const desktopFile = FindDesktopFile();
- if (!desktopFile) {
- cmCPackLogger(cmCPackLog::LOG_WARNING,
- "A desktop file is required to build an AppImage, make sure "
- "it's listed for install()."
- << std::endl);
- return 0;
- }
- {
- cmCPackLogger(cmCPackLog::LOG_OUTPUT,
- "Found Desktop file: \"" << desktopFile.value() << "\""
- << std::endl);
- std::string desktopSymLink = this->toplevel + "/" +
- cmSystemTools::GetFilenameName(desktopFile.value());
- cmCPackLogger(cmCPackLog::LOG_OUTPUT,
- "Desktop file destination: \"" << desktopSymLink << "\""
- << std::endl);
- auto status = cmSystemTools::CreateSymlink(
- cmSystemTools::RelativePath(toplevel, *desktopFile), desktopSymLink);
- if (status.IsSuccess()) {
- cmCPackLogger(cmCPackLog::LOG_DEBUG,
- "Desktop symbolic link created successfully."
- << std::endl);
- } else {
- cmCPackLogger(cmCPackLog::LOG_ERROR,
- "Error creating symbolic link." << status.GetString()
- << std::endl);
- return 0;
- }
- }
- auto const desktopEntry = ParseDesktopFile(*desktopFile);
- {
- // Prepare Icon file
- auto const iconValue = desktopEntry.find("Icon");
- if (iconValue == desktopEntry.end()) {
- cmCPackLogger(cmCPackLog::LOG_ERROR,
- "An Icon key is required to build an AppImage, make sure "
- "the desktop file has a reference to one."
- << std::endl);
- return 0;
- }
- auto icon = this->GetOption("CPACK_PACKAGE_ICON");
- if (!icon) {
- cmCPackLogger(cmCPackLog::LOG_ERROR,
- "CPACK_PACKAGE_ICON is required to build an AppImage."
- << std::endl);
- return 0;
- }
- if (!cmSystemTools::StringStartsWith(*icon, iconValue->second.c_str())) {
- cmCPackLogger(cmCPackLog::LOG_ERROR,
- "CPACK_PACKAGE_ICON must match the file name referenced "
- "in the desktop file."
- << std::endl);
- return 0;
- }
- auto const iconFile = FindFile(icon);
- if (!iconFile) {
- cmCPackLogger(cmCPackLog::LOG_ERROR,
- "Could not find the Icon referenced in the desktop file: "
- << *icon << std::endl);
- return 0;
- }
- cmCPackLogger(cmCPackLog::LOG_OUTPUT,
- "Icon file: \"" << *iconFile << "\"" << std::endl);
- std::string iconSymLink =
- this->toplevel + "/" + cmSystemTools::GetFilenameName(*iconFile);
- cmCPackLogger(cmCPackLog::LOG_OUTPUT,
- "Icon link destination: \"" << iconSymLink << "\""
- << std::endl);
- auto status = cmSystemTools::CreateSymlink(
- cmSystemTools::RelativePath(toplevel, *iconFile), iconSymLink);
- if (status.IsSuccess()) {
- cmCPackLogger(cmCPackLog::LOG_DEBUG,
- "Icon symbolic link created successfully." << std::endl);
- } else {
- cmCPackLogger(cmCPackLog::LOG_ERROR,
- "Error creating symbolic link." << status.GetString()
- << std::endl);
- return 0;
- }
- }
- std::string application;
- {
- // Prepare executable file
- auto const execValue = desktopEntry.find("Exec");
- if (execValue == desktopEntry.end() || execValue->second.empty()) {
- cmCPackLogger(cmCPackLog::LOG_ERROR,
- "An Exec key is required to build an AppImage, make sure "
- "the desktop file has a reference to one."
- << std::endl);
- return 0;
- }
- auto const execName =
- cmSystemTools::SplitString(execValue->second, ' ').front();
- auto const mainExecutable = FindFile(execName);
- if (!mainExecutable) {
- cmCPackLogger(
- cmCPackLog::LOG_ERROR,
- "Could not find the Executable referenced in the desktop file: "
- << execName << std::endl);
- return 0;
- }
- application = cmSystemTools::RelativePath(toplevel, *mainExecutable);
- }
- std::string const appRunFile = this->toplevel + "/AppRun";
- {
- // AppRun script will run our application
- cmGeneratedFileStream appRun(appRunFile);
- appRun << R"sh(#! /usr/bin/env bash
- # autogenerated by CPack
- # make sure errors in sourced scripts will cause this script to stop
- set -e
- this_dir="$(readlink -f "$(dirname "$0")")"
- )sh" << std::endl;
- appRun << R"sh(exec "$this_dir"/)sh" << application << R"sh( "$@")sh"
- << std::endl;
- }
- mode_t permissions;
- {
- auto status = cmSystemTools::GetPermissions(appRunFile, permissions);
- if (!status.IsSuccess()) {
- cmCPackLogger(cmCPackLog::LOG_ERROR,
- "Error getting AppRun permission: " << status.GetString()
- << std::endl);
- return 0;
- }
- }
- auto status =
- cmSystemTools::SetPermissions(appRunFile, permissions | S_IXUSR);
- if (!status.IsSuccess()) {
- cmCPackLogger(cmCPackLog::LOG_ERROR,
- "Error changing AppRun permission: " << status.GetString()
- << std::endl);
- return 0;
- }
- // Set RPATH to "$ORIGIN/../lib"
- if (!ChangeRPath()) {
- return 0;
- }
- // Run appimagetool
- std::vector<std::string> command{
- this->AppimagetoolPath,
- this->toplevel,
- };
- command.emplace_back("../" + *this->GetOption("CPACK_PACKAGE_FILE_NAME") +
- this->GetOutputExtension());
- auto addOptionFlag = [&command, this](std::string const& op,
- std::string commandFlag) {
- auto opt = this->GetOption(op);
- if (opt) {
- command.emplace_back(commandFlag);
- }
- };
- auto addOption = [&command, this](std::string const& op,
- std::string commandFlag) {
- auto opt = this->GetOption(op);
- if (opt) {
- command.emplace_back(commandFlag);
- command.emplace_back(*opt);
- }
- };
- auto addOptions = [&command, this](std::string const& op,
- std::string commandFlag) {
- auto opt = this->GetOption(op);
- if (opt) {
- auto const options = cmSystemTools::SplitString(*opt, ';');
- for (auto const& mkOpt : options) {
- command.emplace_back(commandFlag);
- command.emplace_back(mkOpt);
- }
- }
- };
- addOption("CPACK_APPIMAGE_UPDATE_INFORMATION", "--updateinformation");
- addOptionFlag("CPACK_APPIMAGE_GUESS_UPDATE_INFORMATION", "--guess");
- addOption("CPACK_APPIMAGE_COMPRESSOR", "--comp");
- addOptions("CPACK_APPIMAGE_MKSQUASHFS_OPTIONS", "--mksquashfs-opt");
- addOptionFlag("CPACK_APPIMAGE_NO_APPSTREAM", "--no-appstream");
- addOption("CPACK_APPIMAGE_EXCLUDE_FILE", "--exclude-file");
- addOption("CPACK_APPIMAGE_RUNTIME_FILE", "--runtime-file");
- addOptionFlag("CPACK_APPIMAGE_SIGN", "--sign");
- addOption("CPACK_APPIMAGE_SIGN_KEY", "--sign-key");
- cmCPackLogger(cmCPackLog::LOG_OUTPUT,
- "Running AppImageTool: "
- << cmSystemTools::PrintSingleCommand(command) << std::endl);
- int retVal = 1;
- bool resS = cmSystemTools::RunSingleCommand(
- command, nullptr, nullptr, &retVal, this->toplevel.c_str(),
- cmSystemTools::OutputOption::OUTPUT_PASSTHROUGH);
- if (!resS || retVal) {
- cmCPackLogger(cmCPackLog::LOG_ERROR,
- "Problem running appimagetool: " << this->AppimagetoolPath
- << std::endl);
- return 0;
- }
- return 1;
- }
- cm::optional<std::string> cmCPackAppImageGenerator::FindFile(
- std::string const& filename) const
- {
- for (std::string const& file : this->files) {
- if (cmSystemTools::GetFilenameName(file) == filename) {
- cmCPackLogger(cmCPackLog::LOG_DEBUG, "Found file:" << file << std::endl);
- return file;
- }
- }
- return cm::nullopt;
- }
- cm::optional<std::string> cmCPackAppImageGenerator::FindDesktopFile() const
- {
- cmValue desktopFileOpt = GetOption("CPACK_APPIMAGE_DESKTOP_FILE");
- if (desktopFileOpt) {
- return FindFile(*desktopFileOpt);
- }
- for (std::string const& file : this->files) {
- if (cmSystemTools::StringEndsWith(file, ".desktop")) {
- cmCPackLogger(cmCPackLog::LOG_DEBUG,
- "Found desktop file:" << file << std::endl);
- return file;
- }
- }
- return cm::nullopt;
- }
- namespace {
- // Trim leading and trailing whitespace from a string
- std::string trim(std::string const& str)
- {
- auto start = std::find_if_not(
- str.begin(), str.end(), [](unsigned char c) { return std::isspace(c); });
- auto end = std::find_if_not(str.rbegin(), str.rend(), [](unsigned char c) {
- return std::isspace(c);
- }).base();
- return (start < end) ? std::string(start, end) : std::string();
- }
- } // namespace
- std::unordered_map<std::string, std::string>
- cmCPackAppImageGenerator::ParseDesktopFile(std::string const& filePath) const
- {
- std::unordered_map<std::string, std::string> ret;
- cmsys::ifstream file(filePath);
- if (!file.is_open()) {
- cmCPackLogger(cmCPackLog::LOG_ERROR,
- "Failed to open desktop file:" << filePath << std::endl);
- return ret;
- }
- bool inDesktopEntry = false;
- std::string line;
- while (std::getline(file, line)) {
- line = trim(line);
- if (line.empty() || line[0] == '#') {
- // Skip empty lines or comments
- continue;
- }
- if (line.front() == '[' && line.back() == ']') {
- // We only care for [Desktop Entry] section
- inDesktopEntry = (line == "[Desktop Entry]");
- continue;
- }
- if (inDesktopEntry) {
- size_t delimiter_pos = line.find('=');
- if (delimiter_pos == std::string::npos) {
- cmCPackLogger(cmCPackLog::LOG_WARNING,
- "Invalid desktop file line format: " << line
- << std::endl);
- continue;
- }
- std::string key = trim(line.substr(0, delimiter_pos));
- std::string value = trim(line.substr(delimiter_pos + 1));
- if (!key.empty()) {
- ret.emplace(key, value);
- }
- }
- }
- return ret;
- }
- bool cmCPackAppImageGenerator::ChangeRPath()
- {
- // AppImages are mounted in random locations so we need RPATH to resolve to
- // that location
- std::string const newRPath = "$ORIGIN/../lib";
- for (std::string const& file : this->files) {
- cmELF elf(file.c_str());
- auto const type = elf.GetFileType();
- switch (type) {
- case cmELF::FileType::FileTypeExecutable:
- case cmELF::FileType::FileTypeSharedLibrary: {
- std::string oldRPath;
- auto const* rpath = elf.GetRPath();
- if (rpath) {
- oldRPath = rpath->Value;
- } else {
- auto const* runpath = elf.GetRunPath();
- if (runpath) {
- oldRPath = runpath->Value;
- } else {
- oldRPath = "";
- }
- }
- if (cmSystemTools::StringStartsWith(oldRPath, "$ORIGIN")) {
- // Skip libraries with ORIGIN RPATH set
- continue;
- }
- if (!PatchElfSetRPath(file, newRPath)) {
- return false;
- }
- break;
- }
- default:
- cmCPackLogger(cmCPackLog::LOG_DEBUG,
- "ELF <" << file << "> type: " << type << std::endl);
- break;
- }
- }
- return true;
- }
- bool cmCPackAppImageGenerator::PatchElfSetRPath(std::string const& file,
- std::string const& rpath) const
- {
- cmCPackLogger(cmCPackLog::LOG_DEBUG,
- "Changing RPATH: " << file << " to: " << rpath << std::endl);
- int retVal = 1;
- bool resS = cmSystemTools::RunSingleCommand(
- {
- this->PatchElfPath,
- "--set-rpath",
- rpath,
- file,
- },
- nullptr, nullptr, &retVal, nullptr,
- cmSystemTools::OutputOption::OUTPUT_NONE);
- if (!resS || retVal) {
- cmCPackLogger(cmCPackLog::LOG_ERROR,
- "Problem running patchelf to change RPATH: " << file
- << std::endl);
- return false;
- }
- return true;
- }
|