CPack: Add AppImage generator

This AppImage generator only relies on appimagetool and patchelf.

Closes: #27104
Co-authored-by: Brad King <brad.king@kitware.com>
This commit is contained in:
Daniel Nicoletti
2025-07-30 12:39:43 -03:00
committed by Brad King
parent 9f2949bc68
commit 1a6dbcc9ea
17 changed files with 799 additions and 0 deletions

View File

@@ -1,5 +1,7 @@
set(CMake_TEST_GUI "ON" CACHE BOOL "")
if (NOT "$ENV{CMAKE_CI_NIGHTLY}" STREQUAL "")
set(CMake_TEST_CPACK_APPIMAGE "ON" CACHE STRING "")
set(CMake_TEST_CPACK_APPIMAGE_RUNTIME_FILE "$ENV{CI_PROJECT_DIR}/.gitlab/appimagetool/lib/appimagetool/runtime" CACHE FILEPATH "")
set(CMake_TEST_ISPC "ON" CACHE STRING "")
endif()
set(CMake_TEST_MODULE_COMPILATION "named,compile_commands,collation,partitions,internal_partitions,export_bmi,install_bmi,shared,bmionly,build_database" CACHE STRING "")

View File

@@ -1,3 +1,4 @@
if test "$CMAKE_CI_NIGHTLY" = "true"; then
source .gitlab/ci/appimagetool-env.sh
source .gitlab/ci/ispc-env.sh
fi

130
Help/cpack_gen/appimage.rst Normal file
View File

@@ -0,0 +1,130 @@
CPack AppImage generator
------------------------
.. versionadded:: 4.2
CPack `AppImage`_ generator allows to bundle an application into
AppImage format. It uses ``appimagetool`` to pack the application,
and ``patchelf`` to set the application ``RPATH`` to a relative path
based on where the AppImage will be mounted.
.. _`AppImage`: https://appimage.org
The ``appimagetool`` does not scan for libraries dependencies it only
packs the installed content and check if the provided ``.desktop`` file
was properly created. For best compatibility it's recommended to choose
some old LTS distro and built it there, as well as including most
dependencies on the generated file.
The snipped below can be added to your ``CMakeLists.txt`` file
replacing ``my_application_target`` with your application target,
it will do a best effort to scan and copy the libraries your
application links to and copy to install location.
.. code-block:: cmake
install(CODE [[
file(GET_RUNTIME_DEPENDENCIES
EXECUTABLES $<TARGET_FILE:my_application_target>
RESOLVED_DEPENDENCIES_VAR resolved_deps
)
foreach(dep ${resolved_deps})
# copy the symlink
file(COPY ${dep} DESTINATION ${CMAKE_INSTALL_PREFIX}/lib)
# Resolve the real path of the dependency (follows symlinks)
file(REAL_PATH ${dep} resolved_dep_path)
# Copy the resolved file to the destination
file(COPY ${resolved_dep_path} DESTINATION ${CMAKE_INSTALL_PREFIX}/lib)
endforeach()
]])
For Qt based projects it's recommended to call
``qt_generate_deploy_app_script()`` or ``qt_generate_deploy_qml_app_script()``
and install the files generated by the script, this will install
Qt module's plugins.
You must also set :variable:`CPACK_PACKAGE_ICON` with the same value
listed in the Desktop file.
Variables specific to CPack AppImage generator
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. variable:: CPACK_APPIMAGE_TOOL_EXECUTABLE
Name of the ``appimagetool`` executable, might be located in the build dir,
full path or reachable in ``PATH``.
:Default: ``appimagetool`` :variable:`CPACK_PACKAGE_FILE_NAME`
.. variable:: CPACK_APPIMAGE_PATCHELF_EXECUTABLE
Name of the ``patchelf`` executable, might be located in the build dir,
full path or reachable in ``PATH``.
:Default: ``patchelf`` :variable:`CPACK_APPIMAGE_PATCHELF_EXECUTABLE`
.. variable:: CPACK_APPIMAGE_DESKTOP_FILE
Name of freedesktop.org desktop file installed.
:Mandatory: Yes
:Default: :variable:`CPACK_APPIMAGE_DESKTOP_FILE`
.. variable:: CPACK_APPIMAGE_UPDATE_INFORMATION
Embed update information STRING; if zsyncmake is installed,
generate zsync file.
:Default: :variable:`CPACK_APPIMAGE_UPDATE_INFORMATION`
.. variable:: CPACK_APPIMAGE_GUESS_UPDATE_INFORMATION
Guess update information based on GitHub or GitLab environment variables.
:Default: :variable:`CPACK_APPIMAGE_GUESS_UPDATE_INFORMATION`
.. variable:: CPACK_APPIMAGE_COMPRESSOR
Squashfs compression.
:Default: :variable:`CPACK_APPIMAGE_COMPRESSOR`
.. variable:: CPACK_APPIMAGE_MKSQUASHFS_OPTIONS
Arguments to pass through to mksquashfs.
:Default: :variable:`CPACK_APPIMAGE_MKSQUASHFS_OPTIONS`
.. variable:: CPACK_APPIMAGE_NO_APPSTREAM
Do not check AppStream metadata.
:Default: :variable:`CPACK_APPIMAGE_NO_APPSTREAM`
.. variable:: CPACK_APPIMAGE_EXCLUDE_FILE
Uses given file as exclude file for mksquashfs,
in addition to .appimageignore.
:Default: :variable:`CPACK_APPIMAGE_EXCLUDE_FILE`
.. variable:: CPACK_APPIMAGE_RUNTIME_FILE
Runtime file to use, if not set a bash script will be generated.
:Default: :variable:`CPACK_APPIMAGE_RUNTIME_FILE`
.. variable:: CPACK_APPIMAGE_SIGN
Sign with gpg[2].
:Default: :variable:`CPACK_APPIMAGE_SIGN`
.. variable:: CPACK_APPIMAGE_SIGN_KEY
Key ID to use for gpg[2] signatures.
:Default: :variable:`CPACK_APPIMAGE_SIGN_KEY`

View File

@@ -13,6 +13,7 @@ Generators
.. toctree::
:maxdepth: 1
/cpack_gen/appimage
/cpack_gen/archive
/cpack_gen/bundle
/cpack_gen/cygwin

View File

@@ -1191,7 +1191,9 @@ add_library(
CPack/cmCPackArchiveGenerator.cxx
CPack/cmCPackComponentGroup.cxx
CPack/cmCPackDebGenerator.cxx
CPack/cmCPackDebGenerator.h
CPack/cmCPackExternalGenerator.cxx
CPack/cmCPackExternalGenerator.h
CPack/cmCPackGeneratorFactory.cxx
CPack/cmCPackGenerator.cxx
CPack/cmCPackLog.cxx
@@ -1256,6 +1258,15 @@ if(UNIX)
endif()
endif()
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
target_sources(
CPackLib
PRIVATE
CPack/cmCPackAppImageGenerator.cxx
CPack/cmCPackAppImageGenerator.h
)
endif()
if(CYGWIN)
target_sources(
CPackLib

View File

@@ -0,0 +1,457 @@
/* 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;
}

View File

@@ -0,0 +1,68 @@
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
file LICENSE.rst or https://cmake.org/licensing for details. */
#pragma once
#include <string>
#include <unordered_map>
#include <cm/optional>
#include "cmCPackGenerator.h"
/** \class cmCPackAppImageGenerator
* \brief A generator for creating AppImages with CPack
*/
class cmCPackAppImageGenerator : public cmCPackGenerator
{
public:
cmCPackTypeMacro(cmCPackAppImageGenerator, cmCPackGenerator);
char const* GetOutputExtension() override { return ".AppImage"; }
cmCPackAppImageGenerator();
~cmCPackAppImageGenerator() override;
protected:
/**
* @brief Initializes the CPack engine with our defaults
*/
int InitializeInternal() override;
/**
* @brief AppImages are for single applications
*/
bool SupportsComponentInstallation() const override { return false; }
/**
* Main Packaging step
*/
int PackageFiles() override;
private:
/**
* @brief Finds the first installed file by it's name
*/
cm::optional<std::string> FindFile(std::string const& filename) const;
/**
* @brief AppImage format requires a desktop file
*/
cm::optional<std::string> FindDesktopFile() const;
/**
* @brief Parses a desktop file [Desktop Entry]
*/
std::unordered_map<std::string, std::string> ParseDesktopFile(
std::string const& filePath) const;
/**
* @brief changes the RPATH so that AppImage can find it's libraries
*/
bool ChangeRPath();
bool PatchElfSetRPath(std::string const& file,
std::string const& rpath) const;
std::string AppimagetoolPath;
std::string PatchElfPath;
};

View File

@@ -39,6 +39,10 @@
# include "WiX/cmCPackWIXGenerator.h"
#endif
#ifdef __linux__
# include "cmCPackAppImageGenerator.h"
#endif
cmCPackGeneratorFactory::cmCPackGeneratorFactory()
{
if (cmCPackArchiveGenerator::CanGenerate()) {
@@ -132,6 +136,12 @@ cmCPackGeneratorFactory::cmCPackGeneratorFactory()
cmCPackFreeBSDGenerator::CreateGenerator);
}
#endif
#ifdef __linux__
if (cmCPackAppImageGenerator::CanGenerate()) {
this->RegisterGenerator("AppImage", "AppImage packages",
cmCPackAppImageGenerator::CreateGenerator);
}
#endif
}
std::unique_ptr<cmCPackGenerator> cmCPackGeneratorFactory::NewGenerator(

View File

@@ -1287,6 +1287,12 @@ endif()
add_RunCMake_test_group(CPack "${cpack_tests}")
if(CMake_TEST_CPACK_APPIMAGE)
add_RunCMake_test(CPack_AppImage
-DCMake_TEST_CPACK_APPIMAGE_RUNTIME_FILE=${CMake_TEST_CPACK_APPIMAGE_RUNTIME_FILE}
)
endif()
if(CMake_TEST_CPACK_WIX3 OR CMake_TEST_CPACK_WIX4)
add_RunCMake_test(CPack_WIX
-DCMake_TEST_CPACK_WIX3=${CMake_TEST_CPACK_WIX3}

View File

@@ -0,0 +1,3 @@
if(NOT EXISTS "${RunCMake_TEST_BINARY_DIR}/GeneratorTest-1.2.3-Linux.AppImage")
set(RunCMake_TEST_FAILED "AppImage package not generated")
endif()

View File

@@ -0,0 +1,19 @@
appimagetool[^
]*
Using architecture x86_64
Deleting pre-existing \.DirIcon
Creating \.DirIcon symlink based on information from desktop file
WARNING: AppStream upstream metadata is missing, please consider creating it
in usr/share/metainfo/com\.example\.app\.appdata\.xml
Please see https://www\.freedesktop\.org/software/appstream/docs/chap-Quickstart\.html#sect-Quickstart-DesktopApps
for more information or use the generator at
https://docs\.appimage\.org/packaging-guide/optional/appstream\.html#using-the-appstream-generator
Generating squashfs\.\.\.
Embedding ELF\.\.\.
Marking the AppImage as executable\.\.\.
Embedding MD5 digest
Success
Please consider submitting your AppImage to AppImageHub, the crowd-sourced
central directory of available AppImages, by opening a pull request
at https://github\.com/AppImage/appimage\.github\.io

View File

@@ -0,0 +1,43 @@
CPack: Create package using AppImage
CPack: Install projects
CPack: - Install project: CPackAppImageGenerator \[Release\]
CPack: Create package
CPack: AppDir: "[^"]*/_CPack_Packages/Linux/AppImage/GeneratorTest-1\.2\.3-Linux"
CPack: Found Desktop file: "[^"]*/_CPack_Packages/Linux/AppImage/GeneratorTest-1\.2\.3-Linux/share/applications/com\.example\.app\.desktop"
CPack: Desktop file destination: "[^"]*/_CPack_Packages/Linux/AppImage/GeneratorTest-1\.2\.3-Linux/com\.example\.app\.desktop"
CPack: Icon file: "[^"]*/_CPack_Packages/Linux/AppImage/GeneratorTest-1\.2\.3-Linux/share/icons/hicolor/64x64/apps/ApplicationIcon\.png"
CPack: Icon link destination: "[^"]*/_CPack_Packages/Linux/AppImage/GeneratorTest-1\.2\.3-Linux/ApplicationIcon\.png"
CPack: Running AppImageTool: "[^"]*" "[^"]*/_CPack_Packages/Linux/AppImage/GeneratorTest-1\.2\.3-Linux" "\.\./GeneratorTest-1\.2\.3-Linux\.AppImage" "--runtime-file" "[^"]*"
[^
]*/_CPack_Packages/Linux/AppImage/GeneratorTest-1\.2\.3-Linux should be packaged as \.\./GeneratorTest-1\.2\.3-Linux\.AppImage
Parallel mksquashfs: Using [0-9]+ processors
Creating 4\.0 filesystem on [^
]*/GeneratorTest-1\.2\.3-Linux\.AppImage, block size [0-9]+\.
.*
Exportable Squashfs 4\.0 filesystem, zstd compressed, data block size [0-9]+
[ ]compressed data, compressed metadata, compressed fragments,
[ ]compressed xattrs, compressed ids
[ ]duplicates are removed
Filesystem size [0-9.]+ Kbytes \([0-9.]+ Mbytes\)
[ ][0-9.]+% of uncompressed filesystem size \([0-9.]+ Kbytes\)
Inode table size [0-9]+ bytes \([0-9.]+ Kbytes\)
[ ][0-9.]+% of uncompressed inode table size \([0-9]+ bytes\)
Directory table size [0-9]+ bytes \([0-9.]+ Kbytes\)
[ ][0-9.]+% of uncompressed directory table size \([0-9]+ bytes\)
Number of duplicate files found [0-9]+
Number of inodes [0-9]+
Number of files [0-9]+
Number of fragments [0-9]+
Number of symbolic links [0-9]+
Number of device nodes [0-9]+
Number of fifo nodes [0-9]+
Number of socket nodes [0-9]+
Number of directories [0-9]+
Number of hard-links [0-9]+
Number of ids \(unique uids \+ gids\) [0-9]+
Number of uids [0-9]+
[ ]root \([0-9]+\)
Number of gids [0-9]+
[ ]root \([0-9]+\)
CPack: - package: [^
]*/Tests/RunCMake/CPack_AppImage/AppImageTestApp-build/GeneratorTest-1\.2\.3-Linux\.AppImage generated\.

View File

@@ -0,0 +1,9 @@
include(RunCPack)
set(RunCPack_GENERATORS AppImage)
if(CMake_TEST_CPACK_APPIMAGE_RUNTIME_FILE)
list(APPEND RunCMake_TEST_OPTIONS "-DCPACK_APPIMAGE_RUNTIME_FILE=${CMake_TEST_CPACK_APPIMAGE_RUNTIME_FILE}")
endif()
run_cpack(AppImageTestApp BUILD)

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@@ -0,0 +1,30 @@
cmake_minimum_required(VERSION 4.0)
project(CPackAppImageGenerator
LANGUAGES CXX
VERSION "1.2.3"
)
add_executable(app main.cpp)
install(TARGETS app
RUNTIME
DESTINATION bin
COMPONENT applications)
install(FILES com.example.app.desktop DESTINATION share/applications)
install(FILES ApplicationIcon.png DESTINATION share/icons/hicolor/64x64/apps)
# Create AppImage package
set(CPACK_GENERATOR AppImage)
set(CPACK_PACKAGE_NAME GeneratorTest)
set(CPACK_PACKAGE_VERSION ${CMAKE_PROJECT_VERSION})
set(CPACK_PACKAGE_VENDOR "ACME Inc")
set(CPACK_PACKAGE_DESCRIPTION "An AppImage package for testing CMake's CPack AppImage generator")
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "A test AppImage package")
set(CPACK_PACKAGE_HOMEPAGE_URL "https://www.example.com")
# AppImage generator variables
set(CPACK_PACKAGE_ICON ApplicationIcon.png)
include(CPack)

View File

@@ -0,0 +1,6 @@
[Desktop Entry]
Name=App
Exec=app %u
Icon=ApplicationIcon
Type=Application
Categories=System

View File

@@ -0,0 +1,3 @@
int main()
{
}