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:
Nick Bolton
2024-09-06 21:03:19 +01:00
committed by GitHub
parent df1ab6ef09
commit 735fb0e8c4
16 changed files with 194 additions and 71 deletions

View File

@ -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
View File

@ -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"
},

View File

@ -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

View File

@ -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()

View File

@ -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')

View File

@ -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()

View File

@ -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 $@

View File

@ -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

View File

@ -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);
}

View File

@ -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
}

View File

@ -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 &section);
bool load(const std::string &firstArg);
const char *const *argv() const;
int argc() const;

View File

@ -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;

View File

@ -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

View File

@ -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

View File

@ -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
View 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