diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 7c55683..d7bba6d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -45,7 +45,7 @@ add_subdirectory(util) add_subdirectory(plugin) add_subdirectory(core) -add_executable(linuxdeploy main.cpp) +add_executable(linuxdeploy main.cpp core.cpp) target_link_libraries(linuxdeploy linuxdeploy_core args) set_target_properties(linuxdeploy PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/bin") diff --git a/src/core.cpp b/src/core.cpp new file mode 100644 index 0000000..9ea74d9 --- /dev/null +++ b/src/core.cpp @@ -0,0 +1,88 @@ +#include +#include + +#include +#include + +#include "core.h" + +using namespace linuxdeploy::core; +using namespace linuxdeploy::core::log; +namespace bf = boost::filesystem; + +namespace linuxdeploy { + class DeployError : public std::runtime_error { + public: + explicit DeployError(const std::string& what) : std::runtime_error(what) {}; + }; + + /** + * Resolve the 'MAIN' desktop file from all the available. + * + * @param desktopFilePaths + * @param deployedDesktopFiles + * @return the MAIN DesktopFile + * @throw DeployError in case of 'deployed desktop file not found' + */ + desktopfile::DesktopFile getMainDesktopFile(const std::vector& desktopFilePaths, + const std::vector& deployedDesktopFiles) { + if (desktopFilePaths.empty()) { + ldLog() << LD_WARNING << "No desktop file specified, using first desktop file found:" + << deployedDesktopFiles[0].path() << std::endl; + return deployedDesktopFiles[0]; + } + + auto firstDeployedDesktopFileName = boost::filesystem::path( + desktopFilePaths.front()).filename().string(); + + auto desktopFileMatchingName = find_if( + deployedDesktopFiles.begin(), + deployedDesktopFiles.end(), + [&firstDeployedDesktopFileName](const desktopfile::DesktopFile& desktopFile) { + auto fileName = desktopFile.path().filename().string(); + return fileName == firstDeployedDesktopFileName; + } + ); + + if (desktopFileMatchingName != deployedDesktopFiles.end()) { + return *desktopFileMatchingName; + } else { + ldLog() << LD_ERROR << "Could not find desktop file deployed earlier any more:" + << firstDeployedDesktopFileName << std::endl; + throw DeployError("Old desktop file is not reachable."); + } + } + + bool deployAppDirRootFiles(std::vector desktopFilePaths, + std::string customAppRunPath, appdir::AppDir& appDir) { + ldLog() << std::endl << "-- Deploying files into AppDir root directory --" << std::endl; + + if (!customAppRunPath.empty()) { + ldLog() << LD_INFO << "Deploying custom AppRun: " << customAppRunPath << std::endl; + + const auto& appRunPathInAppDir = appDir.path() / "AppRun"; + if (bf::exists(appRunPathInAppDir)) { + ldLog() << LD_WARNING << "File exists, replacing with custom AppRun" << std::endl; + bf::remove(appRunPathInAppDir); + } + + appDir.deployFile(customAppRunPath, appDir.path() / "AppRun"); + appDir.executeDeferredOperations(); + } + + auto deployedDesktopFiles = appDir.deployedDesktopFiles(); + if (deployedDesktopFiles.empty()) { + ldLog() << LD_WARNING << "Could not find desktop file in AppDir, cannot create links for AppRun, " + "desktop file and icon in AppDir root" << std::endl; + return true; + } + + try { + desktopfile::DesktopFile desktopFile = getMainDesktopFile(desktopFilePaths, deployedDesktopFiles); + ldLog() << "Deploying desktop file:" << desktopFile.path() << std::endl; + return appDir.createLinksInAppDirRoot(desktopFile, customAppRunPath); + } catch (const DeployError& er) { + return false; + } + } +} diff --git a/src/core.h b/src/core.h new file mode 100644 index 0000000..9559689 --- /dev/null +++ b/src/core.h @@ -0,0 +1,20 @@ +#pragma once + +#include +#include + +#include "linuxdeploy/core/appdir.h" + +namespace linuxdeploy { + /** + * Deploy the application ".deskop", icon, and runnable files in the AppDir root path. According to the + * AppDir spec at: https://docs.appimage.org/reference/appdir.html + * + * @param desktopFilePaths to be deployed in the AppDir root + * @param customAppRunPath AppRun to be used, if empty the application executable will be used instead + * @param appDir + * @return true on success otherwise false + */ + bool deployAppDirRootFiles(std::vector desktopFilePaths, std::string customAppRunPath, + linuxdeploy::core::appdir::AppDir& appDir); +} diff --git a/src/main.cpp b/src/main.cpp index a545bbf..47f0d0e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -12,6 +12,7 @@ #include "linuxdeploy/core/log.h" #include "linuxdeploy/plugin/plugin.h" #include "linuxdeploy/util/util.h" +#include "core.h" using namespace linuxdeploy::core; using namespace linuxdeploy::core::log; @@ -253,60 +254,8 @@ int main(int argc, char** argv) { return 1; } } - - // search for desktop file and deploy it to AppDir root - ldLog() << std::endl << "-- Deploying files into AppDir root directory --" << std::endl; - - if (bf::is_regular_file(appDir.path() / "AppRun")) { - if (customAppRunPath) - ldLog() << LD_WARNING << "AppRun exists but custom AppRun specified, overwriting existing AppRun" << std::endl; - else - ldLog() << LD_WARNING << "AppRun exists, skipping deployment" << std::endl; - } else { - auto deployedDesktopFiles = appDir.deployedDesktopFiles(); - - desktopfile::DesktopFile desktopFile; - - if (deployedDesktopFiles.empty()) { - ldLog() << LD_WARNING << "Could not find desktop file in AppDir, cannot create links for AppRun, desktop file and icon in AppDir root" << std::endl; - } else { - if (!desktopFilePaths.Get().empty()) { - auto firstDeployedDesktopFileName = bf::path(desktopFilePaths.Get().front()).filename().string(); - - auto desktopFileMatchingName = std::find_if( - deployedDesktopFiles.begin(), - deployedDesktopFiles.end(), - [&firstDeployedDesktopFileName](const desktopfile::DesktopFile& desktopFile) { - auto fileName = desktopFile.path().filename().string(); - return fileName == firstDeployedDesktopFileName; - } - ); - - if (desktopFileMatchingName != deployedDesktopFiles.end()) { - desktopFile = *desktopFileMatchingName; - } else { - ldLog() << LD_ERROR << "Could not find desktop file deployed earlier any more:" << firstDeployedDesktopFileName << std::endl; - return 1; - } - } else { - desktopFile = deployedDesktopFiles[0]; - ldLog() << LD_WARNING << "No desktop file specified, using first desktop file found:" << desktopFile.path() << std::endl; - } - - ldLog() << "Deploying desktop file:" << desktopFile.path() << std::endl; - - bool rv; - - if (customAppRunPath) { - rv = appDir.createLinksInAppDirRoot(desktopFile, customAppRunPath.Get()); - } else { - rv = appDir.createLinksInAppDirRoot(desktopFile); - } - - if (!rv) - return 1; - } - } + if (!linuxdeploy::deployAppDirRootFiles(desktopFilePaths.Get(), customAppRunPath.Get(), appDir)) + return 1; if (outputPlugins) { for (const auto& pluginName : outputPlugins.Get()) { @@ -342,3 +291,4 @@ int main(int argc, char** argv) { return 0; } + diff --git a/tests/core/CMakeLists.txt b/tests/core/CMakeLists.txt index 58c3fc4..dcef6fc 100644 --- a/tests/core/CMakeLists.txt +++ b/tests/core/CMakeLists.txt @@ -16,3 +16,24 @@ add_test(test_appdir test_appdir) # make sure library and executable are built before test_appdir add_dependencies(test_appdir simple_library simple_executable) + + + +add_executable(test_linuxdeploy test_linuxdeploy.cpp ../../src/core.cpp) +target_link_libraries(test_linuxdeploy PRIVATE linuxdeploy_core args gtest gtest_main) +target_include_directories(test_linuxdeploy PRIVATE ${PROJECT_SOURCE_DIR}/include ${PROJECT_SOURCE_DIR}/src) + +# calculate paths to resources using CMake and hardcode them in the test binary +target_compile_definitions(test_linuxdeploy PRIVATE + -DSIMPLE_LIBRARY_PATH="$" + -DSIMPLE_EXECUTABLE_PATH="$" + -DSIMPLE_DESKTOP_ENTRY_PATH="${CMAKE_CURRENT_SOURCE_DIR}/../data/simple_app.desktop" + -DSIMPLE_ICON_PATH="${CMAKE_CURRENT_SOURCE_DIR}/../data/simple_icon.svg" + -DSIMPLE_FILE_PATH="${CMAKE_CURRENT_SOURCE_DIR}/../data/simple_file.txt" +) + +# register in CTest +add_test(test_linuxdeploy test_linuxdeploy) + +# make sure library and executable are built before test_appdir +add_dependencies(test_linuxdeploy simple_library simple_executable) diff --git a/tests/core/test_linuxdeploy.cpp b/tests/core/test_linuxdeploy.cpp new file mode 100644 index 0000000..042b692 --- /dev/null +++ b/tests/core/test_linuxdeploy.cpp @@ -0,0 +1,99 @@ +#include "gtest/gtest.h" + +#include "core.h" + +using namespace std; +namespace bf = boost::filesystem; + +namespace LinuxDeployTest { + class LinuxDeployTestsFixture : public ::testing::Test { + public: + bf::path tmpAppDir; + bf::path source_executable_path; + bf::path target_executable_path; + + bf::path source_desktop_path; + bf::path target_desktop_path; + + bf::path source_icon_path; + bf::path target_icon_path; + + bf::path source_apprun_path; + bf::path target_apprun_path; + + void SetUp() override { + tmpAppDir = bf::temp_directory_path() / bf::unique_path("linuxdeploy-tests-%%%%-%%%%-%%%%"); + source_executable_path = SIMPLE_EXECUTABLE_PATH; + target_executable_path = tmpAppDir / "usr/bin" / source_executable_path.filename(); + + source_desktop_path = SIMPLE_DESKTOP_ENTRY_PATH; + target_desktop_path = tmpAppDir / "usr/share/applications" / source_desktop_path.filename(); + source_icon_path = SIMPLE_ICON_PATH; + target_icon_path = tmpAppDir / "usr/share/icons/hicolor/scalable/apps" / source_icon_path.filename(); + source_apprun_path = SIMPLE_FILE_PATH; + target_apprun_path = tmpAppDir / "AppRun"; + + create_directories(tmpAppDir); + } + + void TearDown() override { + remove_all(tmpAppDir); + } + + ~LinuxDeployTestsFixture() override = default; + + void listDeployedFiles() { + std::cout << "Files deployed in AppDir:" << std::endl; + bf::recursive_directory_iterator end_itr; // default construction yields past-the-end + for (bf::recursive_directory_iterator itr(tmpAppDir); itr != end_itr; itr++) { + std::cout << relative(itr->path(), tmpAppDir).string() << std::endl; + } + } + + void fillRegularAppDir() { + add_executable(); + add_desktop(); + add_icon(); + } + + bf::path add_executable() const { + create_directories(target_executable_path.parent_path()); + copy_file(source_executable_path, target_executable_path); + + return target_executable_path; + } + + void add_desktop() const { + create_directories(target_desktop_path.parent_path()); + copy_file(source_desktop_path, target_desktop_path); + } + + void add_icon() const { + create_directories(target_icon_path.parent_path()); + copy_file(source_icon_path, target_icon_path); + } + + void add_apprun() const { + copy_file(source_apprun_path, target_apprun_path); + } + }; + + TEST_F(LinuxDeployTestsFixture, deployAppDirRootFilesWithExistentAppRun) { + fillRegularAppDir(); + add_apprun(); + + linuxdeploy::core::appdir::AppDir appDir(tmpAppDir); + linuxdeploy::deployAppDirRootFiles({}, "", appDir); + + ASSERT_TRUE(exists(tmpAppDir / source_desktop_path.filename())); + ASSERT_TRUE(exists(tmpAppDir / source_icon_path.filename())); + ASSERT_TRUE(exists(target_apprun_path)); + } + + TEST_F(LinuxDeployTestsFixture, deployAppDirRootFilesWithCustomAppRun) { + linuxdeploy::core::appdir::AppDir appDir(tmpAppDir); + linuxdeploy::deployAppDirRootFiles({}, source_apprun_path.string(), appDir); + + ASSERT_TRUE(exists(target_apprun_path)); + } +}