Refactor meson build system (#1280)

- Bump meson version to 1.3
- Bump default cpp_std (required by Catch2 3.x, still possible for c++11
fallback)
- Move option `single-file-header` and `precompiled` to `mode`
- Make pkgconfig optional
- Refactor tests, fix a bizzare situation where filename contains quote
  or escape characters
This commit is contained in:
Zephyr Lykos
2026-01-17 09:39:24 +08:00
committed by GitHub
parent aa5360b9ea
commit f0eddc8dc5
9 changed files with 241 additions and 151 deletions

View File

@@ -191,7 +191,7 @@ jobs:
pipx install ninja
- name: Configure
run: meson setup build-meson . -Dtests=true
run: meson setup build-meson . -Dtests=enabled
- name: Build
run: meson compile -C build-meson

View File

@@ -1,3 +1,4 @@
module(name = "cli11")
bazel_dep(name = "rules_cc", version = "0.2.16")
bazel_dep(name = "catch2", version = "3.5.4", dev_dependency = True)

View File

@@ -1,18 +1,13 @@
project('CLI11', ['cpp'],
version : run_command(find_program('scripts/ExtractVersion.py'), check: true).stdout().strip(),
license : 'BSD-3-clause',
meson_version : '>= 0.60',
default_options : ['cpp_std=c++11', 'warning_level=3']
meson_version : '>= 1.3',
default_options : ['cpp_std=c++17,c++14,c++11', 'warning_level=3']
)
cxx = meson.get_compiler('cpp')
use_single_header = get_option('single-file-header')
use_precompiled = get_option('precompiled')
if use_precompiled and use_single_header
error('Options "single-file"header" and "precompiled" are mutually exclusive')
endif
buildmode = get_option('mode')
cli11_headers = files(
'include/CLI/App.hpp',
@@ -47,52 +42,79 @@ cli11_impl_headers = files(
'include/CLI/impl/ExtraValidators_inl.hpp',
)
subdir('single-include')
cli11_inc = include_directories('include')
CLI11_inc = include_directories(['include'])
warnings = ['-Wshadow', '-Wsign-conversion', '-Wswitch-enum']
if cxx.get_id() == 'gcc' and cxx.version().version_compare('>=4.9')
warnings += '-Weffc++'
if cxx.get_argument_syntax() == 'gcc'
warnings = ['-Wshadow', '-Wsign-conversion', '-Wswitch-enum']
if cxx.get_id() == 'gcc' and cxx.version().version_compare('>=4.9')
warnings += '-Weffc++'
endif
if cxx.get_id() == 'clang'
warnings += [
'-Wcast-align',
'-Wimplicit-atomic-properties',
'-Wmissing-declarations',
'-Woverlength-strings',
'-Wstrict-selector-match',
'-Wundeclared-selector',
]
endif
add_project_arguments(cxx.get_supported_arguments(warnings), language: 'cpp')
endif
if cxx.get_id() == 'clang'
warnings += [
'-Wcast-align',
'-Wimplicit-atomic-properties',
'-Wmissing-declarations',
'-Woverlength-strings',
'-Wstrict-selector-match',
'-Wundeclared-selector',
]
endif
add_project_arguments(cxx.get_supported_arguments(warnings), language: 'cpp')
if use_precompiled
if buildmode == 'amalgamated'
subdir('single-include')
cli11_dep = declare_dependency(
sources: single_header,
include_directories : include_directories(meson.current_build_dir() / 'single-include'),
)
elif buildmode == 'headeronly'
install_headers(cli11_headers, subdir: 'CLI')
pkg = import('pkgconfig', required: false)
if pkg.found()
pkg.generate(
name : 'CLI11',
description : 'CLI11 is a command line parser for C++11 and beyond that provides a rich feature set with a simple and intuitive interface.',
url : 'https://github.com/CLIUtils/CLI11',
install_dir : get_option('datadir') / 'pkgconfig',
)
endif
cli11_dep = declare_dependency(include_directories : cli11_inc)
elif buildmode == 'precompiled'
cli11_cflags = ['-DCLI11_COMPILE', '-DCLI11_ENABLE_EXTRA_VALIDATORS=1']
libcli11 = library(
'CLI11',
'src/Precompile.cpp',
include_directories : CLI11_inc,
cpp_args : ['-DCLI11_COMPILE -DCLI11_ENABLE_EXTRA_VALIDATORS=1'],
include_directories : cli11_inc,
cpp_args : cli11_cflags,
install : true,
)
pkg = import('pkgconfig')
pkg.generate(libcli11, extra_cflags: ['-DCLI11_COMPILE -DCLI11_ENABLE_EXTRA_VALIDATORS=1'])
pkg = import('pkgconfig', required: false)
if pkg.found()
pkg.generate(
libcli11,
description : 'CLI11 is a command line parser for C++11 and beyond that provides a rich feature set with a simple and intuitive interface.',
extra_cflags : cli11_cflags,
url : 'https://github.com/CLIUtils/CLI11',
)
endif
install_headers(cli11_headers, subdir: 'CLI')
else
libcli11 = []
cli11_dep = declare_dependency(
link_with : libcli11,
include_directories : cli11_inc,
)
endif
CLI11_dep = declare_dependency(
sources : single_header,
link_with : libcli11,
include_directories : CLI11_inc,
version : meson.project_version(),
)
meson.override_dependency('CLI11', cli11_dep)
meson.override_dependency('CLI11', CLI11_dep)
if get_option('tests')
subdir('tests')
endif
tests = get_option('tests')
tests = tests.disable_auto_if(meson.is_subproject())
catch2_dep = dependency('catch2', required: tests)
subdir('tests', if_found: catch2_dep)

View File

@@ -1,3 +1,2 @@
option('tests', type: 'boolean', value: false, description: 'Build CLI11 tests')
option('single-file-header', type: 'boolean', value: false, description : 'Generate a single header file.')
option('precompiled', type: 'boolean', value: false, description : 'Generate a precompiled static library instead of a header-only')
option('tests', type: 'feature', value: 'auto', description: 'Build CLI11 tests')
option('mode', type: 'combo', choices: ['amalgamated', 'headeronly', 'precompiled'], value: 'headeronly')

View File

@@ -2,23 +2,11 @@
# meson.build here when generating the single file header so that it is placed
# in the correct location.
pymod = import('python')
prog_python = pymod.find_installation()
single_main_file = files('CLI11.hpp.in')
if use_single_header
single_header = custom_target(
'CLI11.hpp',
input: [files('../scripts/MakeSingleHeader.py'), cli11_headers, cli11_impl_headers],
output: 'CLI11.hpp',
command : [prog_python, '@INPUT@', '--main', single_main_file, '--output', '@OUTPUT@'],
depend_files: [single_main_file],
)
else
# the `declare_dependency` needs to have the single_header source as a source
# dependency, to ensure that the generator runs before any attempts to include
# the header happen. Adding an empty list is an idiomatic way to ensure the
# variable exists but does nothing
single_header = []
endif
single_header = custom_target(
'CLI11.hpp',
input: [cli11_headers, cli11_impl_headers],
output: 'CLI11.hpp',
command : [find_program('../scripts/MakeSingleHeader.py'), '@INPUT@', '--main', single_main_file, '--output', '@OUTPUT@'],
depend_files: [single_main_file],
)

View File

@@ -5,19 +5,32 @@
// SPDX-License-Identifier: BSD-3-Clause
#include "app_helper.hpp"
#include <cmath>
#include <array>
#include <cmath>
#include <complex>
#include <cstdint>
#include <cstdlib>
#include <limits>
#include <map>
#include <string>
#include <tuple>
#include <utility>
#include <vector>
#ifdef _WIN32
#define PLATFORM_TEXT(x) _PLATFORM_TEXT(x)
#define _PLATFORM_TEXT(x) L##x
using tchar = wchar_t;
#include <process.h>
#else
#define PLATFORM_TEXT(x) x
using tchar = char;
#include <csignal>
#include <cstring>
#include <spawn.h>
#include <sys/wait.h>
#include <unistd.h>
#endif
TEST_CASE_METHOD(TApp, "OneFlagShort", "[app]") {
app.add_flag("-c,--count");
args = {"-c"};
@@ -2979,38 +2992,107 @@ TEST_CASE("C20_compile", "simple") {
CHECK_FALSE(flag->empty());
}
#ifdef _WIN32
static int spawn_subprocess_win32(const wchar_t *path, wchar_t *commandline) {
STARTUPINFOW si{};
si.cb = sizeof(si);
PROCESS_INFORMATION pi{};
REQUIRE(CreateProcessW(path, commandline, nullptr, nullptr, FALSE, 0, nullptr, nullptr, &si, &pi));
WaitForSingleObject(pi.hProcess, INFINITE);
DWORD exitcode; // NOLINT(cppcoreguidelines-init-variables)
REQUIRE(GetExitCodeProcess(pi.hProcess, &exitcode));
return static_cast<int>(exitcode);
}
#else
static int spawn_subprocess_posix(const char *path, char *const *argv) {
// NOLINTBEGIN(cppcoreguidelines-init-variables)
pid_t pid;
sigset_t old, reset;
struct sigaction sa, oldint, oldquit;
std::memset(&sa, 0, sizeof(sa));
sa.sa_handler = SIG_IGN;
int status = -1, ret;
posix_spawnattr_t attr;
// NOLINTEND(cppcoreguidelines-init-variables)
pthread_testcancel();
sigaction(SIGINT, &sa, &oldint);
sigaction(SIGQUIT, &sa, &oldquit);
sigaddset(&sa.sa_mask, SIGCHLD);
sigprocmask(SIG_BLOCK, &sa.sa_mask, &old);
sigemptyset(&reset);
if(oldint.sa_handler != SIG_IGN)
sigaddset(&reset, SIGINT);
if(oldquit.sa_handler != SIG_IGN)
sigaddset(&reset, SIGQUIT);
posix_spawnattr_init(&attr);
posix_spawnattr_setsigmask(&attr, &old);
posix_spawnattr_setsigdefault(&attr, &reset);
posix_spawnattr_setflags(&attr, POSIX_SPAWN_SETSIGDEF | POSIX_SPAWN_SETSIGMASK);
CHECK((ret = posix_spawn(&pid, path, nullptr, &attr, argv, nullptr)) == 0);
if(ret == 0)
while(waitpid(pid, &status, 0) < 0 && errno != EINTR) {
}
sigaction(SIGINT, &oldint, nullptr);
sigaction(SIGQUIT, &oldquit, nullptr);
sigprocmask(SIG_SETMASK, &old, nullptr);
return status;
}
#endif
static int spawn_app_exe(const tchar *path) {
#ifdef _WIN32
std::wstring args{L"app_exe 1234 false \"hello world\""};
return spawn_subprocess_win32(path, &args[0]);
#else
std::string arg0{"app_exe"};
std::string arg1{"1234"};
std::string arg2{"false"};
std::string arg3{"hello world"};
// NOLINTNEXTLINE(modernize-avoid-c-arrays)
char *const args[] = {&arg0[0], &arg1[0], &arg2[0], &arg3[0], nullptr};
return spawn_subprocess_posix(path, args);
#endif
}
// #845
TEST_CASE("Ensure UTF-8", "[app]") {
const char *commandline = CLI11_ENSURE_UTF8_EXE " 1234 false \"hello world\"";
int retval = std::system(commandline);
auto retval = spawn_app_exe(PLATFORM_TEXT(CLI11_ENSURE_UTF8_EXE));
if(retval == -1) {
FAIL("Executable '" << commandline << "' reported that argv pointer changed where it should not have been");
FAIL("Executable " CLI11_ENSURE_UTF8_EXE " reported that argv pointer changed where it should not have been");
}
if(retval > 0) {
FAIL("Executable '" << commandline << "' reported different argv at index " << (retval - 1));
FAIL("Executable " CLI11_ENSURE_UTF8_EXE " reported different argv at index " << (retval - 1));
}
if(retval != 0) {
FAIL("Executable '" << commandline << "' failed with an unknown return code");
FAIL("Executable " CLI11_ENSURE_UTF8_EXE " failed with an unknown return code");
}
}
// #845
TEST_CASE("Ensure UTF-8 called twice", "[app]") {
const char *commandline = CLI11_ENSURE_UTF8_TWICE_EXE " 1234 false \"hello world\"";
int retval = std::system(commandline);
auto retval = spawn_app_exe(PLATFORM_TEXT(CLI11_ENSURE_UTF8_TWICE_EXE));
if(retval == -1) {
FAIL("Executable '" << commandline << "' reported that argv pointer changed where it should not have been");
FAIL("Executable " CLI11_ENSURE_UTF8_TWICE_EXE
" reported that argv pointer changed where it should not have been");
}
if(retval > 0) {
FAIL("Executable '" << commandline << "' reported different argv at index " << (retval - 1));
FAIL("Executable " CLI11_ENSURE_UTF8_TWICE_EXE " reported different argv at index " << (retval - 1));
}
if(retval != 0) {
FAIL("Executable '" << commandline << "' failed with an unknown return code");
FAIL("Executable " CLI11_ENSURE_UTF8_TWICE_EXE " failed with an unknown return code");
}
}

View File

@@ -37,6 +37,11 @@ cc_test(
"//:cli11",
"@catch2",
],
linkopts = select({
"@rules_cc//cc/compiler:msvc-cl": [],
"@rules_cc//cc/compiler:clang-cl": [],
"//conditions:default": ["-pthread"],
}),
)
[

View File

@@ -213,6 +213,10 @@ if(CMAKE_CXX_STANDARD GREATER 16)
file(REMOVE ${CMAKE_BINARY_DIR}/test_atomic.cpp)
endif()
find_package(Threads)
target_link_libraries(AppTest PRIVATE Threads::Threads)
# Add -Wno-deprecated-declarations to DeprecatedTest
set(no-deprecated-declarations $<$<CXX_COMPILER_ID:MSVC>:/wd4996>
$<$<NOT:$<CXX_COMPILER_ID:MSVC>>:-Wno-deprecated-declarations>)

View File

@@ -1,106 +1,95 @@
catch2 = dependency('catch2')
if catch2.version().version_compare('<3')
if catch2_dep.version().version_compare('< 3')
testmain = static_library(
'catch_main',
'main.cpp', 'catch.hpp',
dependencies: catch2,
dependencies: catch2_dep,
)
testdep = declare_dependency(
link_with: testmain,
dependencies: [catch2, CLI11_dep]
dependencies: [catch2_dep, cli11_dep]
)
else
testdep = declare_dependency(
dependencies: [CLI11_dep, dependency('catch2-with-main')],
dependencies: [cli11_dep, dependency('catch2-with-main')],
compile_args: '-DCLI11_CATCH3'
)
endif
link_test_lib = library(
link_test_lib = static_library(
'link_test_1',
'link_test_1.cpp',
dependencies: CLI11_dep,
dependencies: cli11_dep,
build_by_default: false,
)
if cxx.get_id() == 'msvc'
if cxx.get_argument_syntax() == 'msvc'
nodeprecated = ['/wd4996']
else
nodeprecated = ['-Wno-deprecated-declarations']
endif
boost = dependency('boost', required: false)
if boost.found()
boost_dep = dependency('boost', required: false, disabler: true)
if boost_dep.found()
boost_dep = declare_dependency(
dependencies: boost,
dependencies: boost_dep,
compile_args: '-DCLI11_BOOST_OPTIONAL',
)
maybe_boost_dep = boost_dep
else
boost_dep = declare_dependency()
maybe_boost_dep = declare_dependency()
endif
testnames = [
['HelpersTest', {}],
['ConfigFileTest', {}],
['OptionTypeTest', {}],
['NumericTypeTest', {}],
['SimpleTest', {}],
['AppTest', {}],
['SetTest', {}],
['TransformTest', {}],
['CreationTest', {}],
['SubcommandTest', {}],
['HelpTest', {}],
['FormatterTest', {}],
['NewParseTest', {}],
['OptionalTest', {'dependencies': boost_dep}],
['DeprecatedTest', {'cpp_args': nodeprecated}],
['StringParseTest', {}],
['ComplexTypeTest', {}],
['TrueFalseTest', {}],
['localeTest', {}],
['OptionGroupTest', {}],
['ExtraValidatorsTest', {}],
['EncodingTest', {}],
testnames = {
'HelpersTest': {'workdir': meson.project_build_root()},
'ConfigFileTest': {},
'OptionTypeTest': {},
'NumericTypeTest': {},
'SimpleTest': {},
'AppTest': {},
'SetTest': {},
'TransformTest': {},
'CreationTest': {},
'SubcommandTest': {},
'HelpTest': {},
'FormatterTest': {},
'NewParseTest': {},
'OptionalTest': {'dependencies': maybe_boost_dep},
'BoostOptionTypeTest': {'dependencies': boost_dep},
'DeprecatedTest': {'cpp_args': nodeprecated},
'StringParseTest': {},
'ComplexTypeTest': {},
'TrueFalseTest': {},
'localeTest': {},
'OptionGroupTest': {},
'ExtraValidatorsTest': {},
'EncodingTest': {},
'WindowsTest': {'dependencies': host_machine.system() == 'windows' ? declare_dependency() : disabler()},
# multi-only
['TimerTest', {}],
'TimerTest': {},
# link_test
['link_test_2', {'link_with': link_test_lib}],
]
'link_test_2': {'link_with': link_test_lib},
}
dependent_applications = [
'ensure_utf8',
'ensure_utf8_twice',
]
dependent_applications_definitions = []
dependent_applications_targets = []
foreach app: dependent_applications
app_target = executable(
fs = import('fs')
app_cfgdata = configuration_data()
app_cflags = []
app_tgts = []
foreach app: ['ensure_utf8', 'ensure_utf8_twice']
app_tgt = executable(
app, 'applications'/app + '.cpp',
dependencies: CLI11_dep,
dependencies: cli11_dep,
build_by_default: false,
)
dependent_applications_targets += app_target
dependent_applications_definitions += '-DCLI11_@0@_EXE="@1@/@2@"'.format(
app.to_upper(), meson.current_build_dir(), app_target)
app_cfgdata.set_quoted(app, fs.relative_to(app_tgt.full_path(), meson.current_build_dir()))
app_cflags += '-DCLI11_@0@_EXE=@1@'.format(app.to_upper(), app_cfgdata.get(app))
app_tgts += app_tgt
endforeach
if host_machine.system() == 'windows'
testnames += [['WindowsTest', {}]]
endif
if boost.found()
testnames += [['BoostOptionTypeTest', {'dependencies': boost_dep}]]
endif
foreach n: testnames
name = n[0]
kwargs = n[1]
t = executable(name, name + '.cpp',
cpp_args: kwargs.get('cpp_args', []) + dependent_applications_definitions,
foreach name, kwargs: testnames
test(name, executable(name, name + '.cpp',
cpp_args: app_cflags + kwargs.get('cpp_args', []),
build_by_default: false,
dependencies: [testdep] + kwargs.get('dependencies', []),
link_with: kwargs.get('link_with', [])
)
test(name, t, depends: dependent_applications_targets)
), depends: app_tgts, workdir: kwargs.get('workdir', meson.current_build_dir()))
endforeach