Add --config-toml arg for TOML config file (#7489)
* Add CLI11 lib * Use newer arg parser for to add --config-toml arg * Fix bug where coco doesn't run in elevated console * Improve macro names * Fixed incorrect macro name * Improve coverage for TOML config load * Allow legacy args and use toml config arg in launch.json * Update ChangeLog * Fail coverage workflow on integ test fail * Remove line break
This commit is contained in:
9
.github/workflows/sonarcloud-analysis.yml
vendored
9
.github/workflows/sonarcloud-analysis.yml
vendored
@ -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
|
||||
|
||||
4
.vscode/launch.json
vendored
4
.vscode/launch.json
vendored
@ -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"
|
||||
},
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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()
|
||||
|
||||
18
meson.build
18
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')
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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 $@
|
||||
|
||||
@ -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
|
||||
|
||||
@ -43,6 +43,10 @@
|
||||
#include "base/TMethodJob.h"
|
||||
#endif
|
||||
|
||||
#if WINAPI_CARBON
|
||||
#include "platform/OSXDragSimulator.h"
|
||||
#endif
|
||||
|
||||
#include <charconv>
|
||||
#include <filesystem>
|
||||
#include <iostream>
|
||||
@ -55,12 +59,10 @@
|
||||
#include <ApplicationServices/ApplicationServices.h>
|
||||
#endif
|
||||
|
||||
#if defined(__APPLE__)
|
||||
#include "platform/OSXDragSimulator.h"
|
||||
#if HAVE_CLI11
|
||||
#include <CLI/CLI.hpp>
|
||||
#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);
|
||||
}
|
||||
|
||||
@ -41,10 +41,16 @@ int Config::argc() const { return static_cast<int>(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
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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
|
||||
|
||||
@ -19,7 +19,6 @@
|
||||
|
||||
#include "synergy/Config.h"
|
||||
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
@ -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
|
||||
|
||||
18
subprojects/.gitignore
vendored
18
subprojects/.gitignore
vendored
@ -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
|
||||
|
||||
10
subprojects/cli11.wrap
Normal file
10
subprojects/cli11.wrap
Normal file
@ -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
|
||||
Reference in New Issue
Block a user