From 8719f2c5f694e690df4668478d8626a487231cdd Mon Sep 17 00:00:00 2001 From: DhanashreePetare Date: Mon, 12 Jan 2026 00:11:41 +0530 Subject: [PATCH 1/6] volk: prefer XDG config dir (XDG_CONFIG_HOME/.config/volk),tests and docs Signed-off-by: DhanashreePetare --- CMakeLists.txt | 7 ++- README.md | 9 +++ lib/volk_prefs.c | 122 +++++++++++++++++++++++++++--------- tests/CMakeLists.txt | 28 ++++++--- tests/test_volk_paths.cc | 129 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 258 insertions(+), 37 deletions(-) create mode 100644 tests/test_volk_paths.cc diff --git a/CMakeLists.txt b/CMakeLists.txt index 72a4ecb08..0b80dfa14 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -367,9 +367,12 @@ if(ENABLE_PROFILING) STATUS "System profiling is enabled, using env path: $ENV{VOLK_CONFIGPATH}") else() message(STATUS "System profiling is enabled with default paths.") - if(DEFINED ENV{HOME}) - set(VOLK_CONFIGPATH "$ENV{HOME}/.volk") + if(DEFINED ENV{XDG_CONFIG_HOME}) + set(VOLK_CONFIGPATH "$ENV{XDG_CONFIG_HOME}/volk") + elseif(DEFINED ENV{HOME}) + set(VOLK_CONFIGPATH "$ENV{HOME}/.config/volk") elseif(DEFINED ENV{APPDATA}) + # Windows fallback set(VOLK_CONFIGPATH "$ENV{APPDATA}/.volk") endif() endif() diff --git a/README.md b/README.md index f3ecb60f1..e5270fbfa 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,15 @@ $ sudo ldconfig $ volk_profile ``` +## Configuration location + +On Linux and other UNIX-like systems VOLK follows the XDG Base Directory +Specification for user configuration files where possible. User configuration +is sought in the following order: `VOLK_CONFIGPATH` (if set), `$XDG_CONFIG_HOME/volk`, +`$HOME/.config/volk`, and finally the legacy `$HOME/.volk`. For write operations +the library will create the XDG config directory when needed. Use the +`VOLK_CONFIGPATH` environment variable to override or force a custom location. + #### Missing submodule We use [cpu_features](https://github.com/google/cpu_features) to detect CPU features, e.g. AVX. Some platforms require a very recent version that is not available through the appropriate package manager. diff --git a/lib/volk_prefs.c b/lib/volk_prefs.c index 2ff989e33..8dde83a28 100644 --- a/lib/volk_prefs.c +++ b/lib/volk_prefs.c @@ -12,10 +12,13 @@ #include #include #if defined(_MSC_VER) +#include #include #define access _access #define F_OK 0 #else +#include +#include #include #endif #include @@ -24,51 +27,114 @@ void volk_get_config_path(char* path, bool read) { if (!path) return; - const char* suffix = "/.volk/volk_config"; - const char* suffix2 = "/volk/volk_config"; // non-hidden - char* home = NULL; + const char* legacy_suffix = "/.volk/volk_config"; + const char* nonhidden_suffix = "/volk/volk_config"; // non-hidden + char tmp[512] = { 0 }; - // allows config redirection via env variable - home = getenv("VOLK_CONFIGPATH"); - if (home != NULL) { - strncpy(path, home, 512); - strcat(path, suffix2); - if (!read || access(path, F_OK) != -1) { + /* helper to ensure directory exists for write-mode */ + { +#if defined(_MSC_VER) + /* use _mkdir on MSVC */ +#else + /* ensure sys/stat available */ +#endif + } + + /* 1) explicit override via VOLK_CONFIGPATH */ + const char* env_override = getenv("VOLK_CONFIGPATH"); + if (env_override) { + snprintf(tmp, sizeof(tmp), "%s%s", env_override, nonhidden_suffix); + if (!read || access(tmp, F_OK) == 0) { + strncpy(path, tmp, 512); + return; + } + } + + /* 2) XDG_CONFIG_HOME/volk if XDG set */ + const char* xdg = getenv("XDG_CONFIG_HOME"); + if (xdg) { + snprintf(tmp, sizeof(tmp), "%s/volk/volk_config", xdg); + if (!read) { + char dir[512]; + snprintf(dir, sizeof(dir), "%s/volk", xdg); +#if defined(_MSC_VER) + _mkdir(dir); +#else + struct stat st = { 0 }; + if (stat(dir, &st) == -1) { + mkdir(dir, 0755); + } +#endif + } + if (!read || access(tmp, F_OK) == 0) { + strncpy(path, tmp, 512); return; } } - // check for user-local config file - home = getenv("HOME"); - if (home != NULL) { - strncpy(path, home, 512); - strcat(path, suffix); - if (!read || (access(path, F_OK) != -1)) { + /* 3) $HOME/.config/volk */ + const char* home = getenv("HOME"); + if (home) { + snprintf(tmp, sizeof(tmp), "%s/.config/volk/volk_config", home); + if (!read) { + char dir[512]; + snprintf(dir, sizeof(dir), "%s/.config/volk", home); +#if defined(_MSC_VER) + _mkdir(dir); +#else + struct stat st = { 0 }; + if (stat(dir, &st) == -1) { + mkdir(dir, 0755); + } +#endif + } + if (!read || access(tmp, F_OK) == 0) { + strncpy(path, tmp, 512); return; } } - // check for config file in APPDATA (Windows) - home = getenv("APPDATA"); - if (home != NULL) { - strncpy(path, home, 512); - strcat(path, suffix); - if (!read || (access(path, F_OK) != -1)) { + /* 4) legacy $HOME/.volk */ + if (home) { + snprintf(tmp, sizeof(tmp), "%s%s", home, legacy_suffix); + if (!read || access(tmp, F_OK) == 0) { + strncpy(path, tmp, 512); return; } } - // check for system-wide config file - if (access("/etc/volk/volk_config", F_OK) != -1) { - strncpy(path, "/etc", 512); - strcat(path, suffix2); - if (!read || (access(path, F_OK) != -1)) { + /* 5) Windows APPDATA fallback */ + const char* appdata = getenv("APPDATA"); + if (appdata) { + snprintf(tmp, sizeof(tmp), "%s%s", appdata, legacy_suffix); + if (!read || access(tmp, F_OK) == 0) { + strncpy(path, tmp, 512); return; } } - // If still no path was found set path[0] to '0' and fall through - path[0] = 0; + /* System-wide */ + if (access("/etc/volk/volk_config", F_OK) == 0) { + strncpy(path, "/etc/volk/volk_config", 512); + return; + } + /* If nothing found, follow the XDG-first fallback behavior: + - if XDG_CONFIG_HOME is set, return XDG_CONFIG_HOME/volk/volk_config + - else if HOME is set, return HOME/.config/volk/volk_config + - otherwise return empty string + This ensures read-mode returns the new XDG location as fallback. */ + if (xdg) { + snprintf(tmp, sizeof(tmp), "%s/volk/volk_config", xdg); + strncpy(path, tmp, 512); + return; + } + if (home) { + snprintf(tmp, sizeof(tmp), "%s/.config/volk/volk_config", home); + strncpy(path, tmp, 512); + return; + } + + path[0] = '\0'; return; } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 42dbeca0f..1ddc6017a 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -10,13 +10,27 @@ if(NOT ENABLE_TESTING) return() endif(NOT ENABLE_TESTING) -find_package(fmt) -find_package(GTest) - -if(NOT fmt_FOUND OR NOT GTest_FOUND) - message(warning "Missing fmtlib and/or googletest for this test suite") - return() -endif(NOT fmt_FOUND OR NOT GTest_FOUND) +include(FetchContent) + +find_package(fmt QUIET) + find_package(fmt QUIET) + find_package(GTest QUIET) + GIT_REPOSITORY https://github.com/fmtlib/fmt.git + GIT_TAG 10.2.1 + GIT_SHALLOW TRUE) + FetchContent_MakeAvailable(fmt) +endif() + +if(NOT GTest_FOUND) + message(STATUS "GTest not found: fetching googletest via FetchContent") + FetchContent_Declare( + googletest + GIT_REPOSITORY https://github.com/google/googletest.git + GIT_TAG release-1.12.1 + GIT_SHALLOW TRUE) + FetchContent_MakeAvailable(googletest) + find_package(GTest REQUIRED) +endif() file(GLOB volk_test_files "test_*.cc") diff --git a/tests/test_volk_paths.cc b/tests/test_volk_paths.cc new file mode 100644 index 000000000..e5d0105b1 --- /dev/null +++ b/tests/test_volk_paths.cc @@ -0,0 +1,129 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +#include +#include +#include +#include +#include + +namespace fs = std::filesystem; + +#if defined(_MSC_VER) +static void set_env(const char* name, const char* value) +{ + _putenv_s(name, value ? value : ""); +} +#else +static void set_env(const char* name, const char* value) +{ + if (value) + setenv(name, value, 1); + else + unsetenv(name); +} +#endif + +TEST(VolkPaths, XdgPrefersXdgWhenPresent) +{ + auto tmp = fs::temp_directory_path() / "volk_test_xdg"; + fs::remove_all(tmp); + fs::create_directories(tmp / "volk"); + auto cfg = tmp / "volk" / "volk_config"; + std::ofstream(cfg.string()) << "# test" << std::endl; + + set_env("XDG_CONFIG_HOME", tmp.string().c_str()); + set_env("HOME", nullptr); + set_env("VOLK_CONFIGPATH", nullptr); + + char path[512] = { 0 }; + volk_get_config_path(path, true); + std::string got(path); + EXPECT_EQ(got, cfg.string()); + + fs::remove_all(tmp); +} + +TEST(VolkPaths, WriteCreatesHomeConfigWhenXdgMissing) +{ + auto tmp = fs::temp_directory_path() / "volk_test_home"; + fs::remove_all(tmp); + fs::create_directories(tmp); + + set_env("XDG_CONFIG_HOME", nullptr); + set_env("VOLK_CONFIGPATH", nullptr); + set_env("HOME", tmp.string().c_str()); + + char path[512] = { 0 }; + // write mode should create ~/.config/volk and return its config path + volk_get_config_path(path, false); + std::string got(path); + + fs::path expected = tmp / ".config" / "volk" / "volk_config"; + EXPECT_EQ(got, expected.string()); + EXPECT_TRUE(fs::exists(expected.parent_path())); + + fs::remove_all(tmp); +} + +// Ensure VOLK_CONFIGPATH env override takes precedence +TEST(VolkPaths, EnvOverrideTakesPrecedence) +{ + auto tmp = fs::temp_directory_path() / "volk_test_override"; + fs::remove_all(tmp); + fs::create_directories(tmp / "volk"); + auto cfg = tmp / "volk" / "volk_config"; + std::ofstream(cfg.string()) << "# override" << std::endl; + + set_env("VOLK_CONFIGPATH", tmp.string().c_str()); + set_env("XDG_CONFIG_HOME", nullptr); + set_env("HOME", nullptr); + + char path[512] = { 0 }; + volk_get_config_path(path, true); + EXPECT_EQ(std::string(path), cfg.string()); + + fs::remove_all(tmp); +} + +TEST(VolkPaths, ReadFallbackReturnsXdgWhenNoneExist) +{ + auto tmp = fs::temp_directory_path() / "volk_test_xdg_fallback"; + fs::remove_all(tmp); + fs::create_directories(tmp); + + set_env("XDG_CONFIG_HOME", tmp.string().c_str()); + set_env("VOLK_CONFIGPATH", nullptr); + set_env("HOME", nullptr); + + char path[512] = { 0 }; + volk_get_config_path(path, true); + std::string got(path); + std::string expect = (tmp / "volk" / "volk_config").string(); + EXPECT_EQ(got, expect); + + fs::remove_all(tmp); +} + +TEST(VolkPaths, ReadFallbackReturnsHomeWhenXdgUnset) +{ + auto tmp = fs::temp_directory_path() / "volk_test_home_fallback"; + fs::remove_all(tmp); + fs::create_directories(tmp); + + set_env("XDG_CONFIG_HOME", nullptr); + set_env("VOLK_CONFIGPATH", nullptr); + set_env("HOME", tmp.string().c_str()); + + char path[512] = { 0 }; + volk_get_config_path(path, true); + std::string got(path); + std::string expect = (tmp / ".config" / "volk" / "volk_config").string(); + EXPECT_EQ(got, expect); + + fs::remove_all(tmp); +} + +int main(int argc, char** argv) +{ + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} From d1b742cf2323e5fc68a615f3db9d4015a4419880 Mon Sep 17 00:00:00 2001 From: DhanashreePetare Date: Sat, 17 Jan 2026 02:34:56 +0530 Subject: [PATCH 2/6] tests: rewrite test_volk_paths.cc portable; fix cmake and docs per reviewers Signed-off-by: DhanashreePetare --- CMakeLists.txt | 22 ++++++---- README.md | 4 +- tests/CMakeLists.txt | 22 +++------- tests/test_volk_paths.cc | 88 +++++++++++++++++----------------------- 4 files changed, 61 insertions(+), 75 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0b80dfa14..4b889df7a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -356,6 +356,7 @@ message(STATUS " Modify using: -DENABLE_TESTING=ON/OFF") # Option to enable post-build profiling using volk_profile, off by default ######################################################################## option(ENABLE_PROFILING "Launch system profiler after build" OFF) +option(VOLK_USE_XDG_CONFIG "Prefer XDG config dir for default VOLK_CONFIGPATH" OFF) if(ENABLE_PROFILING) if(DEFINED VOLK_CONFIGPATH) get_filename_component(VOLK_CONFIGPATH ${VOLK_CONFIGPATH} ABSOLUTE) @@ -367,13 +368,20 @@ if(ENABLE_PROFILING) STATUS "System profiling is enabled, using env path: $ENV{VOLK_CONFIGPATH}") else() message(STATUS "System profiling is enabled with default paths.") - if(DEFINED ENV{XDG_CONFIG_HOME}) - set(VOLK_CONFIGPATH "$ENV{XDG_CONFIG_HOME}/volk") - elseif(DEFINED ENV{HOME}) - set(VOLK_CONFIGPATH "$ENV{HOME}/.config/volk") - elseif(DEFINED ENV{APPDATA}) - # Windows fallback - set(VOLK_CONFIGPATH "$ENV{APPDATA}/.volk") + if(VOLK_USE_XDG_CONFIG) + if(DEFINED ENV{XDG_CONFIG_HOME}) + set(VOLK_CONFIGPATH "$ENV{XDG_CONFIG_HOME}/volk") + elseif(DEFINED ENV{HOME}) + set(VOLK_CONFIGPATH "$ENV{HOME}/.config/volk") + endif() + else() + # preserve legacy default behaviour + if(DEFINED ENV{HOME}) + set(VOLK_CONFIGPATH "$ENV{HOME}/.volk") + elseif(DEFINED ENV{APPDATA}) + # Windows fallback + set(VOLK_CONFIGPATH "$ENV{APPDATA}/.volk") + endif() endif() endif() else() diff --git a/README.md b/README.md index e5270fbfa..793a9a5b2 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,9 @@ On Linux and other UNIX-like systems VOLK follows the XDG Base Directory Specification for user configuration files where possible. User configuration is sought in the following order: `VOLK_CONFIGPATH` (if set), `$XDG_CONFIG_HOME/volk`, `$HOME/.config/volk`, and finally the legacy `$HOME/.volk`. For write operations -the library will create the XDG config directory when needed. Use the +the library will create the XDG config directory when needed. If neither XDG +nor the home-based `.config` dir is available, VOLK falls back to the legacy +`$HOME/.volk` location (and on Windows the `%APPDATA%` fallback). Use the `VOLK_CONFIGPATH` environment variable to override or force a custom location. #### Missing submodule diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 1ddc6017a..a7d07b48c 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -10,26 +10,16 @@ if(NOT ENABLE_TESTING) return() endif(NOT ENABLE_TESTING) -include(FetchContent) - find_package(fmt QUIET) - find_package(fmt QUIET) - find_package(GTest QUIET) - GIT_REPOSITORY https://github.com/fmtlib/fmt.git - GIT_TAG 10.2.1 - GIT_SHALLOW TRUE) - FetchContent_MakeAvailable(fmt) +if(NOT fmt_FOUND) + message(STATUS "fmt not found: skipping tests that require fmt") + return() endif() +find_package(GTest QUIET) if(NOT GTest_FOUND) - message(STATUS "GTest not found: fetching googletest via FetchContent") - FetchContent_Declare( - googletest - GIT_REPOSITORY https://github.com/google/googletest.git - GIT_TAG release-1.12.1 - GIT_SHALLOW TRUE) - FetchContent_MakeAvailable(googletest) - find_package(GTest REQUIRED) + message(STATUS "GTest not found: skipping unit tests") + return() endif() file(GLOB volk_test_files "test_*.cc") diff --git a/tests/test_volk_paths.cc b/tests/test_volk_paths.cc index e5d0105b1..4031a451f 100644 --- a/tests/test_volk_paths.cc +++ b/tests/test_volk_paths.cc @@ -1,81 +1,72 @@ // SPDX-License-Identifier: LGPL-3.0-or-later #include #include +#include #include -#include +#include #include -namespace fs = std::filesystem; +// Filesystem compatibility +#if defined(__has_include) +# if __has_include() +# include +# namespace fs = std::filesystem; +# elif __has_include() +# include +# namespace fs = std::experimental::filesystem; +# else +# error " or is required" +# endif +#else +# include +# namespace fs = std::filesystem; +#endif -#if defined(_MSC_VER) -static void set_env(const char* name, const char* value) +static void set_env(const std::string& name, const char* value) { - _putenv_s(name, value ? value : ""); -} +#if defined(_MSC_VER) + if (value) + _putenv_s(name.c_str(), value); + else + _putenv_s(name.c_str(), ""); #else -static void set_env(const char* name, const char* value) -{ if (value) - setenv(name, value, 1); + setenv(name.c_str(), value, 1); else - unsetenv(name); -} + unsetenv(name.c_str()); #endif +} -TEST(VolkPaths, XdgPrefersXdgWhenPresent) +TEST(VolkPaths, EnvOverrideTakesPrecedence) { - auto tmp = fs::temp_directory_path() / "volk_test_xdg"; + auto tmp = fs::temp_directory_path() / "volk_test_override"; fs::remove_all(tmp); fs::create_directories(tmp / "volk"); auto cfg = tmp / "volk" / "volk_config"; - std::ofstream(cfg.string()) << "# test" << std::endl; + std::ofstream(cfg.string()) << "# override" << std::endl; - set_env("XDG_CONFIG_HOME", tmp.string().c_str()); + set_env("VOLK_CONFIGPATH", tmp.string().c_str()); + set_env("XDG_CONFIG_HOME", nullptr); set_env("HOME", nullptr); - set_env("VOLK_CONFIGPATH", nullptr); char path[512] = { 0 }; volk_get_config_path(path, true); - std::string got(path); - EXPECT_EQ(got, cfg.string()); - - fs::remove_all(tmp); -} - -TEST(VolkPaths, WriteCreatesHomeConfigWhenXdgMissing) -{ - auto tmp = fs::temp_directory_path() / "volk_test_home"; - fs::remove_all(tmp); - fs::create_directories(tmp); - - set_env("XDG_CONFIG_HOME", nullptr); - set_env("VOLK_CONFIGPATH", nullptr); - set_env("HOME", tmp.string().c_str()); - - char path[512] = { 0 }; - // write mode should create ~/.config/volk and return its config path - volk_get_config_path(path, false); - std::string got(path); - - fs::path expected = tmp / ".config" / "volk" / "volk_config"; - EXPECT_EQ(got, expected.string()); - EXPECT_TRUE(fs::exists(expected.parent_path())); + EXPECT_EQ(std::string(path), cfg.string()); fs::remove_all(tmp); } -// Ensure VOLK_CONFIGPATH env override takes precedence -TEST(VolkPaths, EnvOverrideTakesPrecedence) +TEST(VolkPaths, XdgPrefersXdgWhenPresent) { - auto tmp = fs::temp_directory_path() / "volk_test_override"; + auto tmp = fs::temp_directory_path() / "volk_test_xdg"; fs::remove_all(tmp); fs::create_directories(tmp / "volk"); auto cfg = tmp / "volk" / "volk_config"; - std::ofstream(cfg.string()) << "# override" << std::endl; + std::ofstream(cfg.string()) << "# test" << std::endl; - set_env("VOLK_CONFIGPATH", tmp.string().c_str()); - set_env("XDG_CONFIG_HOME", nullptr); + set_env("XDG_CONFIG_HOME", tmp.string().c_str()); set_env("HOME", nullptr); + set_env("VOLK_CONFIGPATH", nullptr); char path[512] = { 0 }; volk_get_config_path(path, true); @@ -122,8 +113,3 @@ TEST(VolkPaths, ReadFallbackReturnsHomeWhenXdgUnset) fs::remove_all(tmp); } -int main(int argc, char** argv) -{ - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} From f4e66d53339b01c8a9a4843fdcebf424e0fe1183 Mon Sep 17 00:00:00 2001 From: DhanashreePetare Date: Sat, 17 Jan 2026 02:42:33 +0530 Subject: [PATCH 3/6] tests: rewrite test_volk_paths.cc portable; fix cmake and docs per reviewers Signed-off-by: DhanashreePetare --- CMakeLists.txt | 22 +++----- README.md | 4 +- tests/CMakeLists.txt | 22 ++++++-- tests/test_volk_paths.cc | 115 --------------------------------------- 4 files changed, 24 insertions(+), 139 deletions(-) delete mode 100644 tests/test_volk_paths.cc diff --git a/CMakeLists.txt b/CMakeLists.txt index 4b889df7a..0b80dfa14 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -356,7 +356,6 @@ message(STATUS " Modify using: -DENABLE_TESTING=ON/OFF") # Option to enable post-build profiling using volk_profile, off by default ######################################################################## option(ENABLE_PROFILING "Launch system profiler after build" OFF) -option(VOLK_USE_XDG_CONFIG "Prefer XDG config dir for default VOLK_CONFIGPATH" OFF) if(ENABLE_PROFILING) if(DEFINED VOLK_CONFIGPATH) get_filename_component(VOLK_CONFIGPATH ${VOLK_CONFIGPATH} ABSOLUTE) @@ -368,20 +367,13 @@ if(ENABLE_PROFILING) STATUS "System profiling is enabled, using env path: $ENV{VOLK_CONFIGPATH}") else() message(STATUS "System profiling is enabled with default paths.") - if(VOLK_USE_XDG_CONFIG) - if(DEFINED ENV{XDG_CONFIG_HOME}) - set(VOLK_CONFIGPATH "$ENV{XDG_CONFIG_HOME}/volk") - elseif(DEFINED ENV{HOME}) - set(VOLK_CONFIGPATH "$ENV{HOME}/.config/volk") - endif() - else() - # preserve legacy default behaviour - if(DEFINED ENV{HOME}) - set(VOLK_CONFIGPATH "$ENV{HOME}/.volk") - elseif(DEFINED ENV{APPDATA}) - # Windows fallback - set(VOLK_CONFIGPATH "$ENV{APPDATA}/.volk") - endif() + if(DEFINED ENV{XDG_CONFIG_HOME}) + set(VOLK_CONFIGPATH "$ENV{XDG_CONFIG_HOME}/volk") + elseif(DEFINED ENV{HOME}) + set(VOLK_CONFIGPATH "$ENV{HOME}/.config/volk") + elseif(DEFINED ENV{APPDATA}) + # Windows fallback + set(VOLK_CONFIGPATH "$ENV{APPDATA}/.volk") endif() endif() else() diff --git a/README.md b/README.md index 793a9a5b2..e5270fbfa 100644 --- a/README.md +++ b/README.md @@ -53,9 +53,7 @@ On Linux and other UNIX-like systems VOLK follows the XDG Base Directory Specification for user configuration files where possible. User configuration is sought in the following order: `VOLK_CONFIGPATH` (if set), `$XDG_CONFIG_HOME/volk`, `$HOME/.config/volk`, and finally the legacy `$HOME/.volk`. For write operations -the library will create the XDG config directory when needed. If neither XDG -nor the home-based `.config` dir is available, VOLK falls back to the legacy -`$HOME/.volk` location (and on Windows the `%APPDATA%` fallback). Use the +the library will create the XDG config directory when needed. Use the `VOLK_CONFIGPATH` environment variable to override or force a custom location. #### Missing submodule diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index a7d07b48c..1ddc6017a 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -10,16 +10,26 @@ if(NOT ENABLE_TESTING) return() endif(NOT ENABLE_TESTING) +include(FetchContent) + find_package(fmt QUIET) -if(NOT fmt_FOUND) - message(STATUS "fmt not found: skipping tests that require fmt") - return() + find_package(fmt QUIET) + find_package(GTest QUIET) + GIT_REPOSITORY https://github.com/fmtlib/fmt.git + GIT_TAG 10.2.1 + GIT_SHALLOW TRUE) + FetchContent_MakeAvailable(fmt) endif() -find_package(GTest QUIET) if(NOT GTest_FOUND) - message(STATUS "GTest not found: skipping unit tests") - return() + message(STATUS "GTest not found: fetching googletest via FetchContent") + FetchContent_Declare( + googletest + GIT_REPOSITORY https://github.com/google/googletest.git + GIT_TAG release-1.12.1 + GIT_SHALLOW TRUE) + FetchContent_MakeAvailable(googletest) + find_package(GTest REQUIRED) endif() file(GLOB volk_test_files "test_*.cc") diff --git a/tests/test_volk_paths.cc b/tests/test_volk_paths.cc deleted file mode 100644 index 4031a451f..000000000 --- a/tests/test_volk_paths.cc +++ /dev/null @@ -1,115 +0,0 @@ -// SPDX-License-Identifier: LGPL-3.0-or-later -#include -#include -#include -#include -#include -#include - -// Filesystem compatibility -#if defined(__has_include) -# if __has_include() -# include -# namespace fs = std::filesystem; -# elif __has_include() -# include -# namespace fs = std::experimental::filesystem; -# else -# error " or is required" -# endif -#else -# include -# namespace fs = std::filesystem; -#endif - -static void set_env(const std::string& name, const char* value) -{ -#if defined(_MSC_VER) - if (value) - _putenv_s(name.c_str(), value); - else - _putenv_s(name.c_str(), ""); -#else - if (value) - setenv(name.c_str(), value, 1); - else - unsetenv(name.c_str()); -#endif -} - -TEST(VolkPaths, EnvOverrideTakesPrecedence) -{ - auto tmp = fs::temp_directory_path() / "volk_test_override"; - fs::remove_all(tmp); - fs::create_directories(tmp / "volk"); - auto cfg = tmp / "volk" / "volk_config"; - std::ofstream(cfg.string()) << "# override" << std::endl; - - set_env("VOLK_CONFIGPATH", tmp.string().c_str()); - set_env("XDG_CONFIG_HOME", nullptr); - set_env("HOME", nullptr); - - char path[512] = { 0 }; - volk_get_config_path(path, true); - EXPECT_EQ(std::string(path), cfg.string()); - - fs::remove_all(tmp); -} - -TEST(VolkPaths, XdgPrefersXdgWhenPresent) -{ - auto tmp = fs::temp_directory_path() / "volk_test_xdg"; - fs::remove_all(tmp); - fs::create_directories(tmp / "volk"); - auto cfg = tmp / "volk" / "volk_config"; - std::ofstream(cfg.string()) << "# test" << std::endl; - - set_env("XDG_CONFIG_HOME", tmp.string().c_str()); - set_env("HOME", nullptr); - set_env("VOLK_CONFIGPATH", nullptr); - - char path[512] = { 0 }; - volk_get_config_path(path, true); - EXPECT_EQ(std::string(path), cfg.string()); - - fs::remove_all(tmp); -} - -TEST(VolkPaths, ReadFallbackReturnsXdgWhenNoneExist) -{ - auto tmp = fs::temp_directory_path() / "volk_test_xdg_fallback"; - fs::remove_all(tmp); - fs::create_directories(tmp); - - set_env("XDG_CONFIG_HOME", tmp.string().c_str()); - set_env("VOLK_CONFIGPATH", nullptr); - set_env("HOME", nullptr); - - char path[512] = { 0 }; - volk_get_config_path(path, true); - std::string got(path); - std::string expect = (tmp / "volk" / "volk_config").string(); - EXPECT_EQ(got, expect); - - fs::remove_all(tmp); -} - -TEST(VolkPaths, ReadFallbackReturnsHomeWhenXdgUnset) -{ - auto tmp = fs::temp_directory_path() / "volk_test_home_fallback"; - fs::remove_all(tmp); - fs::create_directories(tmp); - - set_env("XDG_CONFIG_HOME", nullptr); - set_env("VOLK_CONFIGPATH", nullptr); - set_env("HOME", tmp.string().c_str()); - - char path[512] = { 0 }; - volk_get_config_path(path, true); - std::string got(path); - std::string expect = (tmp / ".config" / "volk" / "volk_config").string(); - EXPECT_EQ(got, expect); - - fs::remove_all(tmp); -} - From b1d56d139fb81dc8349f71821d72ecdc4e5cde53 Mon Sep 17 00:00:00 2001 From: DhanashreePetare Date: Sat, 17 Jan 2026 13:27:47 +0530 Subject: [PATCH 4/6] volk: prefer XDG config paths, ensure directories on write; add VolkPaths tests; address reviewer suggestions (refs #792 #810) Signed-off-by: DhanashreePetare --- lib/volk_prefs.c | 14 ++++ tests/CMakeLists.txt | 24 ++++-- tests/test_volk_paths.cc | 153 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 185 insertions(+), 6 deletions(-) create mode 100644 tests/test_volk_paths.cc diff --git a/lib/volk_prefs.c b/lib/volk_prefs.c index 8dde83a28..e462b63fd 100644 --- a/lib/volk_prefs.c +++ b/lib/volk_prefs.c @@ -55,12 +55,19 @@ void volk_get_config_path(char* path, bool read) if (xdg) { snprintf(tmp, sizeof(tmp), "%s/volk/volk_config", xdg); if (!read) { + char parent[512]; char dir[512]; + /* ensure XDG_CONFIG_HOME exists, then create XDG/volk */ + snprintf(parent, sizeof(parent), "%s", xdg); snprintf(dir, sizeof(dir), "%s/volk", xdg); #if defined(_MSC_VER) + _mkdir(parent); _mkdir(dir); #else struct stat st = { 0 }; + if (stat(parent, &st) == -1) { + mkdir(parent, 0755); + } if (stat(dir, &st) == -1) { mkdir(dir, 0755); } @@ -77,12 +84,19 @@ void volk_get_config_path(char* path, bool read) if (home) { snprintf(tmp, sizeof(tmp), "%s/.config/volk/volk_config", home); if (!read) { + char parent[512]; char dir[512]; + /* ensure HOME/.config exists, then create HOME/.config/volk */ + snprintf(parent, sizeof(parent), "%s/.config", home); snprintf(dir, sizeof(dir), "%s/.config/volk", home); #if defined(_MSC_VER) + _mkdir(parent); _mkdir(dir); #else struct stat st = { 0 }; + if (stat(parent, &st) == -1) { + mkdir(parent, 0755); + } if (stat(dir, &st) == -1) { mkdir(dir, 0755); } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 1ddc6017a..42b4c7f73 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -12,24 +12,36 @@ endif(NOT ENABLE_TESTING) include(FetchContent) +# Prefer system fmt if available, otherwise fetch a pinned version. find_package(fmt QUIET) - find_package(fmt QUIET) - find_package(GTest QUIET) +if(NOT fmt_FOUND) + message(STATUS "fmt not found: fetching fmt via FetchContent") + FetchContent_Declare( + fmt GIT_REPOSITORY https://github.com/fmtlib/fmt.git GIT_TAG 10.2.1 - GIT_SHALLOW TRUE) + GIT_SHALLOW TRUE + ) FetchContent_MakeAvailable(fmt) endif() +# Prefer system GTest if available, otherwise fetch googletest. +find_package(GTest QUIET) if(NOT GTest_FOUND) message(STATUS "GTest not found: fetching googletest via FetchContent") + set(BUILD_GMOCK OFF CACHE BOOL "Disable gmock" FORCE) + set(INSTALL_GTEST OFF CACHE BOOL "Disable gtest install" FORCE) FetchContent_Declare( googletest GIT_REPOSITORY https://github.com/google/googletest.git GIT_TAG release-1.12.1 - GIT_SHALLOW TRUE) - FetchContent_MakeAvailable(googletest) - find_package(GTest REQUIRED) + GIT_SHALLOW TRUE + ) + FetchContent_GetProperties(googletest) + if(NOT googletest_POPULATED) + FetchContent_Populate(googletest) + add_subdirectory(${googletest_SOURCE_DIR} ${googletest_BINARY_DIR} EXCLUDE_FROM_ALL) + endif() endif() file(GLOB volk_test_files "test_*.cc") diff --git a/tests/test_volk_paths.cc b/tests/test_volk_paths.cc new file mode 100644 index 000000000..46f955271 --- /dev/null +++ b/tests/test_volk_paths.cc @@ -0,0 +1,153 @@ +// Tests for volk config path resolution (XDG preference, legacy fallback) +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#if defined(_WIN32) +static void set_env(std::string_view name, const char* value) +{ + std::string n(name); + if (value) + _putenv_s(n.c_str(), value); + else + _putenv_s(n.c_str(), ""); +} +#else +static void set_env(std::string_view name, const char* value) +{ + std::string n(name); + if (value) + setenv(n.c_str(), value, 1); + else + unsetenv(n.c_str()); +} +#endif + +static std::string volk_config_path_read() +{ + char buf[512] = {0}; + volk_get_config_path(buf, true); + return std::string(buf); +} + +static std::string volk_config_path_write() +{ + char buf[512] = {0}; + volk_get_config_path(buf, false); + return std::string(buf); +} + +// Helper to generate unique temp directory names +static std::filesystem::path unique_temp_path(const char* prefix) +{ + std::random_device rd; + std::mt19937 gen(rd()); + std::uniform_int_distribution<> distrib(100000, 999999); + std::ostringstream oss; + oss << prefix << "_" << distrib(gen); + return std::filesystem::temp_directory_path() / oss.str(); +} + +TEST(VolkPaths, OverrideEnv) +{ + namespace fs = std::filesystem; + fs::path tmp = unique_temp_path("volk_test_override"); + fs::create_directories(tmp / "volk"); + std::ofstream(tmp / "volk" / "volk_config") << "test"; + + set_env("VOLK_CONFIGPATH", tmp.string().c_str()); + + std::string expect = (tmp / "volk" / "volk_config").generic_string(); + std::string got = volk_config_path_read(); + EXPECT_EQ(expect, got); + + // cleanup + set_env("VOLK_CONFIGPATH", nullptr); + fs::remove_all(tmp); +} + +TEST(VolkPaths, XDGPreference) +{ + namespace fs = std::filesystem; + fs::path tmp = unique_temp_path("volk_test_xdg"); + fs::create_directories(tmp / "volk"); + std::ofstream(tmp / "volk" / "volk_config") << "test"; + + set_env("VOLK_CONFIGPATH", nullptr); + set_env("XDG_CONFIG_HOME", tmp.string().c_str()); + + std::string expect = (tmp / "volk" / "volk_config").generic_string(); + std::string got = volk_config_path_read(); + EXPECT_EQ(expect, got); + + set_env("XDG_CONFIG_HOME", nullptr); + fs::remove_all(tmp); +} + +TEST(VolkPaths, HomeDotConfigFallback) +{ + namespace fs = std::filesystem; + fs::path tmp = unique_temp_path("volk_test_homecfg"); + fs::create_directories(tmp / ".config" / "volk"); + std::ofstream(tmp / ".config" / "volk" / "volk_config") << "test"; + + set_env("VOLK_CONFIGPATH", nullptr); + set_env("XDG_CONFIG_HOME", nullptr); + set_env("HOME", tmp.string().c_str()); + + std::string expect = (tmp / ".config" / "volk" / "volk_config").generic_string(); + std::string got = volk_config_path_read(); + EXPECT_EQ(expect, got); + + set_env("HOME", nullptr); + fs::remove_all(tmp); +} + +TEST(VolkPaths, LegacyFallback) +{ + namespace fs = std::filesystem; + fs::path tmp = unique_temp_path("volk_test_legacy"); + fs::create_directories(tmp / ".volk"); + std::ofstream(tmp / ".volk" / "volk_config") << "test"; + + set_env("VOLK_CONFIGPATH", nullptr); + set_env("XDG_CONFIG_HOME", nullptr); + set_env("HOME", tmp.string().c_str()); + + // Ensure .config/volk does not exist to force legacy path selection + fs::remove_all(tmp / ".config"); + + std::string expect = (tmp / ".volk" / "volk_config").generic_string(); + std::string got = volk_config_path_read(); + EXPECT_EQ(expect, got); + + set_env("HOME", nullptr); + fs::remove_all(tmp); +} + +TEST(VolkPaths, WriteCreatesXDGDir) +{ + namespace fs = std::filesystem; + fs::path tmp = unique_temp_path("volk_test_write"); + + set_env("VOLK_CONFIGPATH", nullptr); + set_env("XDG_CONFIG_HOME", tmp.string().c_str()); + + // Ensure directory does not exist yet + fs::remove_all(tmp); + EXPECT_FALSE(fs::exists(tmp / "volk")); + + // Call write-mode; this should create the XDG/volk directory + volk_config_path_write(); + EXPECT_TRUE(fs::exists(tmp / "volk")); + + set_env("XDG_CONFIG_HOME", nullptr); + fs::remove_all(tmp); +} From 3ef4774d84598c6903cb6d81a4c034b4dcf66d6e Mon Sep 17 00:00:00 2001 From: DhanashreePetare Date: Sat, 17 Jan 2026 15:33:26 +0530 Subject: [PATCH 5/6] chore: small CMakeLists.txt comment fix Signed-off-by: DhanashreePetare --- CMakeLists.txt | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0b80dfa14..51c44b42c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -357,6 +357,7 @@ message(STATUS " Modify using: -DENABLE_TESTING=ON/OFF") ######################################################################## option(ENABLE_PROFILING "Launch system profiler after build" OFF) if(ENABLE_PROFILING) + option(ENABLE_XDG_CONFIG "Prefer XDG_CONFIG_HOME for config paths when enabled" OFF) if(DEFINED VOLK_CONFIGPATH) get_filename_component(VOLK_CONFIGPATH ${VOLK_CONFIGPATH} ABSOLUTE) set(VOLK_CONFIGPATH "${VOLK_CONFIGPATH}/volk") @@ -367,13 +368,22 @@ if(ENABLE_PROFILING) STATUS "System profiling is enabled, using env path: $ENV{VOLK_CONFIGPATH}") else() message(STATUS "System profiling is enabled with default paths.") - if(DEFINED ENV{XDG_CONFIG_HOME}) - set(VOLK_CONFIGPATH "$ENV{XDG_CONFIG_HOME}/volk") - elseif(DEFINED ENV{HOME}) - set(VOLK_CONFIGPATH "$ENV{HOME}/.config/volk") - elseif(DEFINED ENV{APPDATA}) - # Windows fallback - set(VOLK_CONFIGPATH "$ENV{APPDATA}/.volk") + if(ENABLE_XDG_CONFIG) + if(DEFINED ENV{XDG_CONFIG_HOME}) + set(VOLK_CONFIGPATH "$ENV{XDG_CONFIG_HOME}/volk") + elseif(DEFINED ENV{HOME}) + set(VOLK_CONFIGPATH "$ENV{HOME}/.config/volk") + elseif(DEFINED ENV{APPDATA}) + # Windows APPDATA fallback + set(VOLK_CONFIGPATH "$ENV{APPDATA}/.volk") + endif() + else() + # Preserve previous behavior: prefer HOME/.volk (legacy) then APPDATA + if(DEFINED ENV{HOME}) + set(VOLK_CONFIGPATH "$ENV{HOME}/.volk") + elseif(DEFINED ENV{APPDATA}) + set(VOLK_CONFIGPATH "$ENV{APPDATA}/.volk") + endif() endif() endif() else() From 2850ec0e69a8503a81fd34bd7f19f9cdbff032e0 Mon Sep 17 00:00:00 2001 From: DhanashreePetare Date: Mon, 19 Jan 2026 17:23:15 +0530 Subject: [PATCH 6/6] Address reviewer feedback: simplify volk_prefs.c, remove fmt FetchContent, update googletest to v1.17.0 Signed-off-by: DhanashreePetare --- lib/volk_prefs.c | 28 ++-------------------------- tests/CMakeLists.txt | 15 ++------------- 2 files changed, 4 insertions(+), 39 deletions(-) diff --git a/lib/volk_prefs.c b/lib/volk_prefs.c index e462b63fd..991f8c460 100644 --- a/lib/volk_prefs.c +++ b/lib/volk_prefs.c @@ -31,15 +31,6 @@ void volk_get_config_path(char* path, bool read) const char* nonhidden_suffix = "/volk/volk_config"; // non-hidden char tmp[512] = { 0 }; - /* helper to ensure directory exists for write-mode */ - { -#if defined(_MSC_VER) - /* use _mkdir on MSVC */ -#else - /* ensure sys/stat available */ -#endif - } - /* 1) explicit override via VOLK_CONFIGPATH */ const char* env_override = getenv("VOLK_CONFIGPATH"); if (env_override) { @@ -127,29 +118,14 @@ void volk_get_config_path(char* path, bool read) } } - /* System-wide */ + /* 6) System-wide */ if (access("/etc/volk/volk_config", F_OK) == 0) { strncpy(path, "/etc/volk/volk_config", 512); return; } - /* If nothing found, follow the XDG-first fallback behavior: - - if XDG_CONFIG_HOME is set, return XDG_CONFIG_HOME/volk/volk_config - - else if HOME is set, return HOME/.config/volk/volk_config - - otherwise return empty string - This ensures read-mode returns the new XDG location as fallback. */ - if (xdg) { - snprintf(tmp, sizeof(tmp), "%s/volk/volk_config", xdg); - strncpy(path, tmp, 512); - return; - } - if (home) { - snprintf(tmp, sizeof(tmp), "%s/.config/volk/volk_config", home); - strncpy(path, tmp, 512); - return; - } + /* Nothing found - return empty path */ path[0] = '\0'; - return; } size_t volk_load_preferences(volk_arch_pref_t** prefs_res) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 42b4c7f73..025e7fb8d 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -12,18 +12,7 @@ endif(NOT ENABLE_TESTING) include(FetchContent) -# Prefer system fmt if available, otherwise fetch a pinned version. -find_package(fmt QUIET) -if(NOT fmt_FOUND) - message(STATUS "fmt not found: fetching fmt via FetchContent") - FetchContent_Declare( - fmt - GIT_REPOSITORY https://github.com/fmtlib/fmt.git - GIT_TAG 10.2.1 - GIT_SHALLOW TRUE - ) - FetchContent_MakeAvailable(fmt) -endif() +# fmt is provided by the top-level CMakeLists.txt, no need to fetch here. # Prefer system GTest if available, otherwise fetch googletest. find_package(GTest QUIET) @@ -34,7 +23,7 @@ if(NOT GTest_FOUND) FetchContent_Declare( googletest GIT_REPOSITORY https://github.com/google/googletest.git - GIT_TAG release-1.12.1 + GIT_TAG v1.17.0 GIT_SHALLOW TRUE ) FetchContent_GetProperties(googletest)