Skip to content

Commit 18c7a0b

Browse files
drowaudioclaude
andcommitted
Swap magic_args for CLI11: bind options directly to the settings struct
Addresses review feedback that the magic_args version duplicated the settings into a parallel PluginvalCliArgs struct plus manual CLI->JSON and ENV->JSON bridges (5-6 edit sites per new option), and forced cxx_std_23 + a macOS 13.3 deployment target (std::format). CLI11 binds each option directly to a PluginvalSettings member, with the environment variable on the same line via ->envname(). Adding an option is now three edits: a struct member, a nlohmann macro entry, and one add_option(...) line. No parallel struct, no cliArgsToJson, no envLayer. - SettingsParser: rewritten on CLI11; comma lists via ->delimiter(','), enum via CheckedTransformer, hex/int seed via a small callback. --config seeds the struct before parse so precedence stays defaults<config<env<CLI. --help/--version handled by CLI11 (auto usage + footer). - SettingsSerializer: dropped the comma/JSON-array coercions; kept the hex seed + disabled-tests(file-or-list) helpers and JSON load/save. - Child-process base64 JSON handoff (--config-base64) unchanged. - CommandLineTests: env tests now use setenv/unsetenv (CLI11 reads the real environment); all other cases unchanged. All --run-tests pass. - CMake: magic_args -> CLI11 2.6.2; revert cxx_std_23 -> cxx_std_20 and CMAKE_OSX_DEPLOYMENT_TARGET 13.3 -> 10.11. - Docs/CLAUDE.md updated. Verified: full build (validator ON, C++20, macOS 10.11), --run-tests pass, real --validate SUCCESS, --config precedence covered by tests. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 0822214 commit 18c7a0b

9 files changed

Lines changed: 297 additions & 381 deletions

CLAUDE.md

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
- **Version**: 1.0.4 (see `VERSION` file)
88
- **License**: GPLv3
99
- **Framework**: Built on JUCE (v8.0.x)
10-
- **Language**: C++23
10+
- **Language**: C++20
1111

1212
### Key Features
1313
- Tests VST/VST2/VST3/AU/LV2/LADSPA plugins
@@ -125,7 +125,7 @@ pluginval/
125125

126126
### Prerequisites
127127
- CMake 3.15+
128-
- C++23 compatible compiler
128+
- C++20 compatible compiler
129129
- Git (for submodules)
130130

131131
### Building
@@ -159,7 +159,7 @@ VST2_SDK_DIR=/path/to/vst2sdk cmake -B Builds/Debug .
159159
```
160160

161161
### Target Platforms
162-
- **macOS**: 13.3+ (deployment target — required by std::format used in magic_args), supports Apple Silicon via universal binary
162+
- **macOS**: 10.11+ (deployment target), supports Apple Silicon via universal binary
163163
- **Windows**: MSVC with static runtime linking
164164
- **Linux**: Ubuntu 22.04+, statically links libstdc++
165165

@@ -193,26 +193,30 @@ VST2_SDK_DIR=/path/to/vst2sdk cmake -B Builds/Debug .
193193

194194
### CLI Settings Pipeline
195195

196-
Command-line parsing is a layered JSON-merge pipeline rather than a bespoke
197-
parser. The flow (in `SettingsParser`):
196+
Command-line parsing centres on one plain settings struct (`PluginvalSettings`)
197+
that CLI11 binds to directly. The flow (in `SettingsParser`):
198198

199199
1. **preprocess** the raw command line — rewrite the deprecated `strictnessLevel`,
200200
strip the macOS `-NSDocumentRevisionsDebugMode YES` flag, and insert an
201201
implicit `--validate` when the last argument is a bare plugin path.
202-
2. Build a **sparse `nlohmann::json` per layer**: `--config` file, environment
203-
variables, and the CLI options (parsed with **magic_args** into a struct of
204-
`std::optional` fields, then coerced — comma lists → arrays, hex/int seed →
205-
number, etc. via `SettingsSerializer`).
206-
3. **Merge** with `merge_patch` in precedence order (defaults < config < env <
207-
CLI; **CLI wins**), then deserialise into `PluginvalSettings`
208-
(`NLOHMANN_DEFINE_TYPE..._WITH_DEFAULT` fills missing keys from defaults).
202+
2. If `--config <file.json>` is present, **seed** the struct from that JSON first.
203+
3. **CLI11** binds each `add_option`/`add_flag` straight to a `PluginvalSettings`
204+
member, with the environment variable on the same line via `->envname()`.
205+
Because CLI11 only overwrites a member when its flag/env was actually provided,
206+
precedence is **defaults < config < env < CLI** (CLI wins) with no manual
207+
layering. Comma lists use `->delimiter(',')`, the enum uses a
208+
`CheckedTransformer`, and the hex/int seed is a small callback.
209209
4. `PluginvalSettings::toPluginTestOptions()` converts to the JUCE-flavoured
210210
`PluginTests::Options` at the boundary.
211211

212+
Adding a new option is three edits: a struct member, an entry in the nlohmann
213+
macro list, and one `add_option(...)` line. `SettingsSerializer` handles JSON
214+
load/save plus the two remaining conversions (hex seed, disabled-tests file).
215+
212216
The child validation process receives a fully-resolved, **authoritative**
213217
settings set via a base64-encoded JSON argument (`--config-base64`), avoiding
214218
per-flag re-serialisation and command-line quoting hazards. `--help`/`--version`
215-
are handled by magic_args (auto usage + an appended env-var trailer).
219+
are handled by CLI11 (auto usage + a footer with the env-var/commands notes).
216220

217221
### Test Framework
218222

@@ -401,7 +405,7 @@ add_pluginval_tests(MyPluginTarget
401405
### External
402406
- **JUCE** (v8.0.x) - Audio application framework (git submodule)
403407
- **magic_enum** (v0.9.7) - Enum reflection (fetched via CPM)
404-
- **magic_args** (v0.2.1) - C++23 CLI argument parsing, header-only (fetched via CPM); requires macOS 13.3+ due to std::format
408+
- **CLI11** (v2.6.2) - CLI argument parsing, header-only (fetched via CPM)
405409
- **nlohmann/json** (3.12.0) - JSON settings layering/serialisation (fetched via CPM)
406410
- **rtcheck** (optional, macOS) - Real-time safety checking (fetched via CPM)
407411
- **VST3 SDK** (v3.7.x) - Steinberg VST3 SDK for embedded validator (fetched via CPM, optional)

CMakeLists.txt

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,8 @@ project(pluginval VERSION ${CURRENT_VERSION})
66
# Just compliing pluginval
77
if (pluginval_IS_TOP_LEVEL)
88
if (APPLE)
9-
# Target OS versions down to 13.3. std::format (used by magic_args) pulls in
10-
# libc++'s floating-point formatter, whose to_chars is only available on 13.3+.
11-
set (CMAKE_OSX_DEPLOYMENT_TARGET "13.3" CACHE INTERNAL "")
9+
# Target OS versions down to 10.11
10+
set (CMAKE_OSX_DEPLOYMENT_TARGET "10.11" CACHE INTERNAL "")
1211

1312
# Uncomment to produce a universal binary
1413
# set(CMAKE_OSX_ARCHITECTURES arm64 x86_64)
@@ -62,7 +61,7 @@ option(JUCE_ENABLE_MODULE_SOURCE_GROUPS "Enable Module Source Groups" ON)
6261

6362
include(cmake/CPM.cmake)
6463
CPMAddPackage("gh:Neargye/magic_enum#v0.9.7")
65-
CPMAddPackage("gh:fredemmott/magic_args#v0.2.1")
64+
CPMAddPackage("gh:CLIUtils/CLI11@2.6.2")
6665
CPMAddPackage("gh:nlohmann/json@3.12.0")
6766

6867
if(PLUGINVAL_ENABLE_RTCHECK)
@@ -112,7 +111,7 @@ juce_add_gui_app(pluginval
112111

113112
juce_generate_juce_header(pluginval)
114113

115-
target_compile_features(pluginval PRIVATE cxx_std_23)
114+
target_compile_features(pluginval PRIVATE cxx_std_20)
116115

117116
set_target_properties(pluginval PROPERTIES
118117
C_VISIBILITY_PRESET hidden
@@ -185,7 +184,7 @@ target_link_libraries(pluginval PRIVATE
185184
juce::juce_audio_utils
186185
juce::juce_recommended_warning_flags
187186
magic_enum
188-
magic_args::magic_args
187+
CLI11::CLI11
189188
nlohmann_json::nlohmann_json)
190189

191190
if (${CMAKE_SYSTEM_NAME} MATCHES "Linux")

Source/CommandLine.cpp

Lines changed: 13 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -196,20 +196,6 @@ void performCommandLine (CommandLineValidator& validator, const juce::String& co
196196
auto& app = *juce::JUCEApplication::getInstance();
197197
const auto tokens = settings_parser::preprocess (commandLine);
198198

199-
if (tokens.contains ("--help") || tokens.contains ("-h"))
200-
{
201-
settings_parser::printHelp (app.getApplicationName());
202-
app.quit();
203-
return;
204-
}
205-
206-
if (tokens.contains ("--version"))
207-
{
208-
std::cout << settings_parser::getVersionString() << std::endl;
209-
app.quit();
210-
return;
211-
}
212-
213199
if (tokens.contains ("--run-tests"))
214200
{
215201
runUnitTests();
@@ -230,10 +216,20 @@ void performCommandLine (CommandLineValidator& validator, const juce::String& co
230216
return;
231217
}
232218

233-
// Otherwise this is a validation run (explicit or implicit --validate)
219+
// Otherwise this is a validation run (explicit or implicit --validate).
220+
// CLI11 handles --help/--version and parse errors.
234221
try
235222
{
236-
auto [fileOrID, options] = parseCommandLine (commandLine);
223+
const auto result = settings_parser::parseTokens (tokens);
224+
225+
if (result.handled)
226+
{
227+
app.setApplicationReturnValue (result.exitCode);
228+
app.quit();
229+
return;
230+
}
231+
232+
const auto fileOrID = juce::String (result.settings.validatePath);
237233

238234
if (fileOrID.isEmpty())
239235
{
@@ -242,7 +238,7 @@ void performCommandLine (CommandLineValidator& validator, const juce::String& co
242238
}
243239

244240
// --validate runs async so will quit itself when done
245-
validator.validate (fileOrID, options);
241+
validator.validate (fileOrID, result.settings.toPluginTestOptions());
246242
}
247243
catch (const std::exception& e)
248244
{

0 commit comments

Comments
 (0)