mirror of
https://github.com/Kitware/CMake.git
synced 2026-04-29 18:51:05 -05:00
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:
committed by
Brad King
parent
9f2949bc68
commit
1a6dbcc9ea
@@ -1,5 +1,7 @@
|
|||||||
set(CMake_TEST_GUI "ON" CACHE BOOL "")
|
set(CMake_TEST_GUI "ON" CACHE BOOL "")
|
||||||
if (NOT "$ENV{CMAKE_CI_NIGHTLY}" STREQUAL "")
|
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 "")
|
set(CMake_TEST_ISPC "ON" CACHE STRING "")
|
||||||
endif()
|
endif()
|
||||||
set(CMake_TEST_MODULE_COMPILATION "named,compile_commands,collation,partitions,internal_partitions,export_bmi,install_bmi,shared,bmionly,build_database" CACHE STRING "")
|
set(CMake_TEST_MODULE_COMPILATION "named,compile_commands,collation,partitions,internal_partitions,export_bmi,install_bmi,shared,bmionly,build_database" CACHE STRING "")
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
if test "$CMAKE_CI_NIGHTLY" = "true"; then
|
if test "$CMAKE_CI_NIGHTLY" = "true"; then
|
||||||
|
source .gitlab/ci/appimagetool-env.sh
|
||||||
source .gitlab/ci/ispc-env.sh
|
source .gitlab/ci/ispc-env.sh
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -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`
|
||||||
@@ -13,6 +13,7 @@ Generators
|
|||||||
.. toctree::
|
.. toctree::
|
||||||
:maxdepth: 1
|
:maxdepth: 1
|
||||||
|
|
||||||
|
/cpack_gen/appimage
|
||||||
/cpack_gen/archive
|
/cpack_gen/archive
|
||||||
/cpack_gen/bundle
|
/cpack_gen/bundle
|
||||||
/cpack_gen/cygwin
|
/cpack_gen/cygwin
|
||||||
|
|||||||
@@ -1191,7 +1191,9 @@ add_library(
|
|||||||
CPack/cmCPackArchiveGenerator.cxx
|
CPack/cmCPackArchiveGenerator.cxx
|
||||||
CPack/cmCPackComponentGroup.cxx
|
CPack/cmCPackComponentGroup.cxx
|
||||||
CPack/cmCPackDebGenerator.cxx
|
CPack/cmCPackDebGenerator.cxx
|
||||||
|
CPack/cmCPackDebGenerator.h
|
||||||
CPack/cmCPackExternalGenerator.cxx
|
CPack/cmCPackExternalGenerator.cxx
|
||||||
|
CPack/cmCPackExternalGenerator.h
|
||||||
CPack/cmCPackGeneratorFactory.cxx
|
CPack/cmCPackGeneratorFactory.cxx
|
||||||
CPack/cmCPackGenerator.cxx
|
CPack/cmCPackGenerator.cxx
|
||||||
CPack/cmCPackLog.cxx
|
CPack/cmCPackLog.cxx
|
||||||
@@ -1256,6 +1258,15 @@ if(UNIX)
|
|||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
||||||
|
target_sources(
|
||||||
|
CPackLib
|
||||||
|
PRIVATE
|
||||||
|
CPack/cmCPackAppImageGenerator.cxx
|
||||||
|
CPack/cmCPackAppImageGenerator.h
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
if(CYGWIN)
|
if(CYGWIN)
|
||||||
target_sources(
|
target_sources(
|
||||||
CPackLib
|
CPackLib
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
};
|
||||||
@@ -39,6 +39,10 @@
|
|||||||
# include "WiX/cmCPackWIXGenerator.h"
|
# include "WiX/cmCPackWIXGenerator.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef __linux__
|
||||||
|
# include "cmCPackAppImageGenerator.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
cmCPackGeneratorFactory::cmCPackGeneratorFactory()
|
cmCPackGeneratorFactory::cmCPackGeneratorFactory()
|
||||||
{
|
{
|
||||||
if (cmCPackArchiveGenerator::CanGenerate()) {
|
if (cmCPackArchiveGenerator::CanGenerate()) {
|
||||||
@@ -132,6 +136,12 @@ cmCPackGeneratorFactory::cmCPackGeneratorFactory()
|
|||||||
cmCPackFreeBSDGenerator::CreateGenerator);
|
cmCPackFreeBSDGenerator::CreateGenerator);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef __linux__
|
||||||
|
if (cmCPackAppImageGenerator::CanGenerate()) {
|
||||||
|
this->RegisterGenerator("AppImage", "AppImage packages",
|
||||||
|
cmCPackAppImageGenerator::CreateGenerator);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<cmCPackGenerator> cmCPackGeneratorFactory::NewGenerator(
|
std::unique_ptr<cmCPackGenerator> cmCPackGeneratorFactory::NewGenerator(
|
||||||
|
|||||||
@@ -1287,6 +1287,12 @@ endif()
|
|||||||
|
|
||||||
add_RunCMake_test_group(CPack "${cpack_tests}")
|
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)
|
if(CMake_TEST_CPACK_WIX3 OR CMake_TEST_CPACK_WIX4)
|
||||||
add_RunCMake_test(CPack_WIX
|
add_RunCMake_test(CPack_WIX
|
||||||
-DCMake_TEST_CPACK_WIX3=${CMake_TEST_CPACK_WIX3}
|
-DCMake_TEST_CPACK_WIX3=${CMake_TEST_CPACK_WIX3}
|
||||||
|
|||||||
@@ -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()
|
||||||
@@ -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
|
||||||
@@ -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\.
|
||||||
@@ -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 |
@@ -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)
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
[Desktop Entry]
|
||||||
|
Name=App
|
||||||
|
Exec=app %u
|
||||||
|
Icon=ApplicationIcon
|
||||||
|
Type=Application
|
||||||
|
Categories=System
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
int main()
|
||||||
|
{
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user