diff --git a/.github/workflows/sonarcloud-analysis.yml b/.github/workflows/sonarcloud-analysis.yml index 8e7149df7..958d896fe 100644 --- a/.github/workflows/sonarcloud-analysis.yml +++ b/.github/workflows/sonarcloud-analysis.yml @@ -81,14 +81,7 @@ jobs: - name: Integration tests coverage env: QT_QPA_PLATFORM: offscreen - run: | - cmake --build build --target coverage-integtests - result=$? - - if [ $result -ne 0 ]; then - echo "::warning ::Integration tests failed with code: $result" - fi - continue-on-error: true + run: cmake --build build --target coverage-integtests - name: Get coverage report paths id: coverage-paths diff --git a/.vscode/launch.json b/.vscode/launch.json index 8a1881be7..6514a7284 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -31,6 +31,7 @@ "cwd": "${workspaceRoot}", "request": "launch", "program": "${workspaceFolder}/build/bin/synergys", + "args": ["--config-toml", "synergy-config.toml"], "preLaunchTask": "kill-build" }, { @@ -39,6 +40,7 @@ "cwd": "${workspaceRoot}", "request": "launch", "program": "${workspaceFolder}/build/bin/synergyc", + "args": ["--config-toml", "synergy-config.toml"], "preLaunchTask": "kill-build" }, { @@ -80,6 +82,7 @@ "cwd": "${workspaceRoot}", "request": "launch", "program": "${workspaceFolder}/build/bin/synergys", + "args": ["--config-toml", "synergy-config.toml"], "internalConsoleOptions": "openOnSessionStart", "preLaunchTask": "kill-build" }, @@ -89,6 +92,7 @@ "cwd": "${workspaceRoot}", "request": "launch", "program": "${workspaceFolder}/build/bin/synergyc", + "args": ["--config-toml", "synergy-config.toml"], "internalConsoleOptions": "openOnSessionStart", "preLaunchTask": "kill-build" }, diff --git a/ChangeLog b/ChangeLog index a7ed9c0cf..acb1de6bd 100644 --- a/ChangeLog +++ b/ChangeLog @@ -18,6 +18,7 @@ Enhancements: - #7479 Add `BUILD.md` to get people started - #7485 Use `.venv` dir for as Python venv and cache - #7488 Only wait for elevated process to end when arg is set +- #7489 Add `--config-toml` arg for TOML config file # 1.15.1 diff --git a/cmake/Libraries.cmake b/cmake/Libraries.cmake index cc133b7c3..529cab14b 100644 --- a/cmake/Libraries.cmake +++ b/cmake/Libraries.cmake @@ -14,6 +14,7 @@ macro(configure_libs) configure_openssl() configure_coverage() configure_tomlplusplus() + configure_cli11() if(BUILD_TESTS) configure_gtest() @@ -632,3 +633,14 @@ macro(configure_tomlplusplus) message(WARNING "Subproject 'tomlplusplus' not found") endif() endmacro() + +macro(configure_cli11) + file(GLOB CLI11_DIR ${CMAKE_SOURCE_DIR}/subprojects/CLI11-*) + if(CLI11_DIR) + set(HAVE_CLI11 true) + add_definitions(-DHAVE_CLI11=1) + include_directories(${CLI11_DIR}/include) + else() + message(WARNING "Subproject 'CLI11' not found") + endif() +endmacro() diff --git a/meson.build b/meson.build index 10e0f295f..79784b0ac 100644 --- a/meson.build +++ b/meson.build @@ -1,8 +1,17 @@ # For now, we're only using Meson to resolve dependencies. CMake is called separately. # In future, we may completely replace CMake with Meson. +# Where available, we use system packages, otherwise we use subprojects. +# Subprojects are also used to get the latest version during development. project('synergy', 'cpp') +subproject('tomlplusplus') +subproject('cli11') + +if host_machine.system() == 'windows' + subproject('wintoast') +endif + system_gtest = get_option('system_gtest') if system_gtest dependency('gtest', required: false) @@ -10,15 +19,6 @@ else subproject('gtest') endif -# tomlplusplus: Header-only library -subproject('tomlplusplus') - -if host_machine.system() == 'windows' - # WinToast is a niche lib which is not commonly installed, - # so depend only on the subproject. - subproject('wintoast') -endif - if host_machine.system() == 'linux' system_libei = get_option('system_libei') diff --git a/scripts/install_deps.py b/scripts/install_deps.py index 6dd7287fa..5ec73959a 100755 --- a/scripts/install_deps.py +++ b/scripts/install_deps.py @@ -221,17 +221,20 @@ class Dependencies: import lib.windows as windows if not self.args.skip_elevated: - if not windows.is_admin(): - windows.run_elevated( - __file__, "--only-elevated --skip-python", wait_for_exit=True - ) - elif self.args.only_elevated: + if windows.is_admin(): + # The choco command should run from the elevated command. choco = windows.WindowsChoco() choco.ensure_choco_installed() command_elevated = self.config.get_os_deps_command("command-elevated") cmd_utils.run(command_elevated, shell=True, print_cmd=True) - sys.exit(0) + + if self.args.only_elevated: + sys.exit(0) + else: + windows.run_elevated( + __file__, "--only-elevated --skip-python", wait_for_exit=True + ) qt = qt_utils.WindowsQt(*self.config.get_qt_config()) qt.install() diff --git a/scripts/install_deps.sh b/scripts/install_deps.sh index 8462b70e2..b44fb3803 100755 --- a/scripts/install_deps.sh +++ b/scripts/install_deps.sh @@ -10,7 +10,7 @@ install_deps() { NetBSD*) install_netbsd ;; DragonFly*) install_dragonfly ;; SunOS*) install_solaris ;; - *) install_other $uname_out ;; + *) install_other $@ ;; esac } @@ -64,8 +64,7 @@ install_solaris() { install_other() { # TODO: Port the .py script to shell script to make the deps installation lighter on # Linux and macOS. The .py script is probably only really needed to deal with Windows. - echo "Running Python script for: $1" - ./scripts/install_deps.py + ./scripts/install_deps.py $@ } run_cmd() { @@ -74,4 +73,4 @@ run_cmd() { $cmd } -install_deps +install_deps $@ diff --git a/src/lib/common/constants.h b/src/lib/common/constants.h index 1438e7138..3fb21b52d 100644 --- a/src/lib/common/constants.h +++ b/src/lib/common/constants.h @@ -23,6 +23,7 @@ #endif const auto kAppName = "Synergy"; +const auto kAppDescription = "Mouse and keyboard sharing utility"; const auto kVersion = SYNERGY_VERSION; #ifdef GIT_SHA_SHORT diff --git a/src/lib/synergy/App.cpp b/src/lib/synergy/App.cpp index 354e77167..32d5447d9 100644 --- a/src/lib/synergy/App.cpp +++ b/src/lib/synergy/App.cpp @@ -43,6 +43,10 @@ #include "base/TMethodJob.h" #endif +#if WINAPI_CARBON +#include "platform/OSXDragSimulator.h" +#endif + #include #include #include @@ -55,12 +59,10 @@ #include #endif -#if defined(__APPLE__) -#include "platform/OSXDragSimulator.h" +#if HAVE_CLI11 +#include #endif -const auto kConfigFilename = "synergy-config.toml"; - using namespace synergy; App *App::s_instance = nullptr; @@ -182,9 +184,23 @@ void App::loggingFilterWarning() { void App::initApp(int argc, const char **argv) { - Config config(kConfigFilename, configSection()); - if (config.load(argv[0])) { - parseArgs(config.argc(), config.argv()); + std::string configFilename; +#if HAVE_CLI11 + CLI::App cliApp{kAppDescription, kAppName}; + cliApp.add_option( + "--config-toml", configFilename, "Use TOML configuration file"); + + // Allow legacy args. + cliApp.allow_extras(); + + cliApp.parse(argc, argv); +#endif // HAVE_CLI11 + + if (!configFilename.empty()) { + Config config(configFilename, configSection()); + if (config.load(argv[0])) { + parseArgs(config.argc(), config.argv()); + } } else { parseArgs(argc, argv); } diff --git a/src/lib/synergy/Config.cpp b/src/lib/synergy/Config.cpp index b5f2d1543..9538e697e 100644 --- a/src/lib/synergy/Config.cpp +++ b/src/lib/synergy/Config.cpp @@ -41,10 +41,16 @@ int Config::argc() const { return static_cast(m_argv.size()); } bool Config::load(const std::string &firstArg) { #if HAVE_TOMLPLUSPLUS - m_args.push_back(firstArg); + if (!firstArg.empty()) { + m_args.push_back(firstArg); + } - if (m_filename.empty() || !std::filesystem::exists(m_filename)) { - LOG((CLOG_DEBUG "no config file at: %s", m_filename.c_str())); + if (m_filename.empty()) { + throw NoConfigFilenameError(); + } + + if (!std::filesystem::exists(m_filename)) { + LOG((CLOG_ERR "config file not found: %s", m_filename.c_str())); return false; } @@ -102,7 +108,7 @@ bool Config::load(const std::string &firstArg) { return true; #else - LOG((CLOG_WARN "toml++ not available, config file not loaded")); + LOG((CLOG_ERR "toml++ not available, config file not loaded")); return false; #endif // HAVE_TOMLPLUSPLUS } diff --git a/src/lib/synergy/Config.h b/src/lib/synergy/Config.h index d7542684e..7c96504e2 100644 --- a/src/lib/synergy/Config.h +++ b/src/lib/synergy/Config.h @@ -32,13 +32,20 @@ Initially this class was created to as a developer convenience; it is a convenient place to specify args without needing to fiddle with IDE configs. */ class Config { +public: class ParseError : public std::runtime_error { public: explicit ParseError() : std::runtime_error("failed to parse config file") {} }; -public: + class NoConfigFilenameError : public std::runtime_error { + public: + explicit NoConfigFilenameError() + : std::runtime_error("no config file specified") {} + }; + explicit Config(const std::string &filename, const std::string §ion); + bool load(const std::string &firstArg); const char *const *argv() const; int argc() const; diff --git a/src/lib/synergy/ServerApp.cpp b/src/lib/synergy/ServerApp.cpp index 7fee752d7..fca7fbee0 100644 --- a/src/lib/synergy/ServerApp.cpp +++ b/src/lib/synergy/ServerApp.cpp @@ -120,9 +120,9 @@ void ServerApp::parseArgs(int argc, const char *const *argv) { void ServerApp::help() { const auto userConfig = - ARCH->concatPath(ARCH->getUserDirectory(), USR_CONFIG_NAME); + ARCH->concatPath(ARCH->getUserDirectory(), USER_CONFIG_NAME); const auto sysConfig = - ARCH->concatPath(ARCH->getSystemDirectory(), SYS_CONFIG_NAME); + ARCH->concatPath(ARCH->getSystemDirectory(), SYSTEM_CONFIG_NAME); std::stringstream help; help @@ -205,7 +205,7 @@ void ServerApp::loadConfig() { path = ARCH->getUserDirectory(); if (!path.empty()) { // complete path - path = ARCH->concatPath(path, USR_CONFIG_NAME); + path = ARCH->concatPath(path, USER_CONFIG_NAME); // now try loading the user's configuration if (loadConfig(path)) { @@ -217,7 +217,7 @@ void ServerApp::loadConfig() { // try the system-wide config file path = ARCH->getSystemDirectory(); if (!path.empty()) { - path = ARCH->concatPath(path, SYS_CONFIG_NAME); + path = ARCH->concatPath(path, SYSTEM_CONFIG_NAME); if (loadConfig(path)) { loaded = true; args().m_configFile = path; diff --git a/src/lib/synergy/ServerApp.h b/src/lib/synergy/ServerApp.h index 4d7828e19..9ddda0c30 100644 --- a/src/lib/synergy/ServerApp.h +++ b/src/lib/synergy/ServerApp.h @@ -140,9 +140,9 @@ private: // configuration file name #if SYSAPI_WIN32 -#define USR_CONFIG_NAME "synergy.sgc" -#define SYS_CONFIG_NAME "synergy.sgc" +#define USER_CONFIG_NAME "synergy.sgc" +#define SYSTEM_CONFIG_NAME "synergy.sgc" #elif SYSAPI_UNIX -#define USR_CONFIG_NAME ".synergy.conf" -#define SYS_CONFIG_NAME "synergy.conf" +#define USER_CONFIG_NAME ".synergy.conf" +#define SYSTEM_CONFIG_NAME "synergy.conf" #endif diff --git a/src/test/integtests/synergy/ConfigTests.cpp b/src/test/integtests/synergy/ConfigTests.cpp index d97573630..896257129 100644 --- a/src/test/integtests/synergy/ConfigTests.cpp +++ b/src/test/integtests/synergy/ConfigTests.cpp @@ -19,7 +19,6 @@ #include "synergy/Config.h" -#include #include #include @@ -27,26 +26,102 @@ using namespace synergy; const auto kTestFilename = "tmp/test/test.toml"; -TEST(ConfigTests, LoadConfigFile) { +TEST(ConfigTests, load_fileExists_loadsConfig) { std::ofstream testFile(kTestFilename); testFile << "[test.args]\n" R"(test-arg = "test opt")"; testFile.close(); + Config config(kTestFilename, "test"); - try { - Config config(kTestFilename, "test"); + const auto result = config.load("test"); - ASSERT_TRUE(config.load("test")); - ASSERT_EQ(config.argc(), 3); - ASSERT_STREQ(config.argv()[0], "test"); - ASSERT_STREQ(config.argv()[1], "--test-arg"); - ASSERT_STREQ(config.argv()[2], "test opt"); + ASSERT_TRUE(result); + ASSERT_EQ(config.argc(), 3); + ASSERT_STREQ(config.argv()[0], "test"); + ASSERT_STREQ(config.argv()[1], "--test-arg"); + ASSERT_STREQ(config.argv()[2], "test opt"); +} - } catch (const std::exception &e) { - FAIL() << e.what(); - } +TEST(ConfigTests, load_filenameEmpty_throwsException) { + EXPECT_THROW( + { + Config config("", "test"); - std::filesystem::remove(kTestFilename); + config.load("test"); + }, + Config::NoConfigFilenameError); +} + +TEST(ConfigTests, load_fileDoesNotExist_returnsFalse) { + Config config("nonexistent.toml", "test"); + + const auto result = config.load("test"); + + ASSERT_FALSE(result); +} + +TEST(ConfigTests, load_invalidConfig_throwsException) { + EXPECT_THROW( + { + std::ofstream testFile(kTestFilename); + testFile << "foobar"; + testFile.close(); + + Config config(kTestFilename, "test"); + + config.load("test"); + }, + Config::ParseError); +} + +TEST(ConfigTests, load_sectionMissing_returnsFalse) { + std::ofstream testFile(kTestFilename); + testFile.close(); + Config config(kTestFilename, "missing"); + + const auto result = config.load("test"); + + ASSERT_FALSE(result); +} + +TEST(ConfigTests, load_notTable_returnsFalse) { + std::ofstream testFile(kTestFilename); + testFile << "[test]"; + testFile.close(); + Config config(kTestFilename, "test"); + + const auto result = config.load("test"); + + ASSERT_FALSE(result); +} + +TEST(ConfigTests, load_lastArg_returnsLast) { + std::ofstream testFile(kTestFilename); + testFile << "[test.args]\n" + R"(_last = "test last")" + "\n" + R"(test-second = true)"; + testFile.close(); + Config config(kTestFilename, "test"); + + const auto result = config.load("test-first"); + + ASSERT_TRUE(result); + ASSERT_EQ(config.argc(), 3); + ASSERT_STREQ(config.argv()[0], "test-first"); + ASSERT_STREQ(config.argv()[1], "--test-second"); + ASSERT_STREQ(config.argv()[2], "test last"); +} + +TEST(ConfigTests, load_noArgs_returnsFalse) { + std::ofstream testFile(kTestFilename); + testFile << "[test.args]"; + testFile.close(); + Config config(kTestFilename, "test"); + + const auto result = config.load(""); + + ASSERT_FALSE(result); } #endif // HAVE_TOMLPLUSPLUS diff --git a/subprojects/.gitignore b/subprojects/.gitignore index 74e30399f..3b6b452bf 100644 --- a/subprojects/.gitignore +++ b/subprojects/.gitignore @@ -1,14 +1,10 @@ -# Fetched dependencies. -/packagecache -/googletest-* -/WinToast-* -/libei -/libportal -/gi-docgen -/munit -/dotenv-cpp -/tomlplusplus-* +# Ignore all files in the subprojects directory. +* -# Added by dependencies. +# Except for the wrap files. +!.gitignore +!*.wrap + +# Ignore wraps added by subprojects. /gi-docgen.wrap /munit.wrap diff --git a/subprojects/cli11.wrap b/subprojects/cli11.wrap new file mode 100644 index 000000000..dd26d5924 --- /dev/null +++ b/subprojects/cli11.wrap @@ -0,0 +1,10 @@ +[wrap-file] +directory = CLI11-2.4.1 +source_url = https://github.com/CLIUtils/CLI11/archive/refs/tags/v2.4.1.tar.gz +source_filename = CLI11-2.4.1.tar.gz +source_hash = 73b7ec52261ce8fe980a29df6b4ceb66243bb0b779451dbd3d014cfec9fdbb58 +source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/cli11_2.4.1-1/CLI11-2.4.1.tar.gz +wrapdb_version = 2.4.1-1 + +[provide] +cli11 = CLI11_dep