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

@@ -12,7 +12,7 @@ concurrency:
permissions: permissions:
contents: read contents: read
env: env:
CTEST_OUTPUT_ON_FAILURE: "1" CTEST_OUTPUT_ON_FAILURE: "1"
@@ -191,7 +191,7 @@ jobs:
pipx install ninja pipx install ninja
- name: Configure - name: Configure
run: meson setup build-meson . -Dtests=true run: meson setup build-meson . -Dtests=enabled
- name: Build - name: Build
run: meson compile -C build-meson run: meson compile -C build-meson
@@ -263,7 +263,7 @@ jobs:
- name: Run tests - name: Run tests
run: ctest --output-on-failure -L Packaging run: ctest --output-on-failure -L Packaging
working-directory: build working-directory: build
install-precompiled-macos: install-precompiled-macos:
name: install tests precompiled macos name: install tests precompiled macos
runs-on: macos-15 runs-on: macos-15
@@ -280,7 +280,7 @@ jobs:
- name: Run tests - name: Run tests
run: ctest --output-on-failure -L Packaging run: ctest --output-on-failure -L Packaging
working-directory: build working-directory: build
install-precompiled-macos-no-validators: install-precompiled-macos-no-validators:
name: install tests precompiled macos no validators name: install tests precompiled macos no validators
runs-on: macos-15 runs-on: macos-15
@@ -314,7 +314,7 @@ jobs:
- name: Run tests - name: Run tests
run: ctest --output-on-failure -L Packaging run: ctest --output-on-failure -L Packaging
working-directory: build working-directory: build
install-module: install-module:
name: install module tests name: install module tests
runs-on: ubuntu-latest runs-on: ubuntu-latest
@@ -472,7 +472,7 @@ jobs:
with: with:
cmake-version: "4.1" cmake-version: "4.1"
if: success() || failure() if: success() || failure()
- name: Check CMake 4.2 - name: Check CMake 4.2
uses: ./.github/actions/quick_cmake uses: ./.github/actions/quick_cmake
with: with:

View File

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

View File

@@ -1,18 +1,13 @@
project('CLI11', ['cpp'], project('CLI11', ['cpp'],
version : run_command(find_program('scripts/ExtractVersion.py'), check: true).stdout().strip(), version : run_command(find_program('scripts/ExtractVersion.py'), check: true).stdout().strip(),
license : 'BSD-3-clause', license : 'BSD-3-clause',
meson_version : '>= 0.60', meson_version : '>= 1.3',
default_options : ['cpp_std=c++11', 'warning_level=3'] default_options : ['cpp_std=c++17,c++14,c++11', 'warning_level=3']
) )
cxx = meson.get_compiler('cpp') cxx = meson.get_compiler('cpp')
use_single_header = get_option('single-file-header') buildmode = get_option('mode')
use_precompiled = get_option('precompiled')
if use_precompiled and use_single_header
error('Options "single-file"header" and "precompiled" are mutually exclusive')
endif
cli11_headers = files( cli11_headers = files(
'include/CLI/App.hpp', 'include/CLI/App.hpp',
@@ -47,52 +42,79 @@ cli11_impl_headers = files(
'include/CLI/impl/ExtraValidators_inl.hpp', 'include/CLI/impl/ExtraValidators_inl.hpp',
) )
subdir('single-include') cli11_inc = include_directories('include')
CLI11_inc = include_directories(['include']) if cxx.get_argument_syntax() == 'gcc'
warnings = ['-Wshadow', '-Wsign-conversion', '-Wswitch-enum']
warnings = ['-Wshadow', '-Wsign-conversion', '-Wswitch-enum'] if cxx.get_id() == 'gcc' and cxx.version().version_compare('>=4.9')
if cxx.get_id() == 'gcc' and cxx.version().version_compare('>=4.9') warnings += '-Weffc++'
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 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( libcli11 = library(
'CLI11', 'CLI11',
'src/Precompile.cpp', 'src/Precompile.cpp',
include_directories : CLI11_inc, include_directories : cli11_inc,
cpp_args : ['-DCLI11_COMPILE -DCLI11_ENABLE_EXTRA_VALIDATORS=1'], cpp_args : cli11_cflags,
install : true, install : true,
) )
pkg = import('pkgconfig') pkg = import('pkgconfig', required: false)
pkg.generate(libcli11, extra_cflags: ['-DCLI11_COMPILE -DCLI11_ENABLE_EXTRA_VALIDATORS=1']) 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') install_headers(cli11_headers, subdir: 'CLI')
else
libcli11 = [] cli11_dep = declare_dependency(
link_with : libcli11,
include_directories : cli11_inc,
)
endif endif
CLI11_dep = declare_dependency( meson.override_dependency('CLI11', cli11_dep)
sources : single_header,
link_with : libcli11,
include_directories : CLI11_inc,
version : meson.project_version(),
)
meson.override_dependency('CLI11', CLI11_dep) tests = get_option('tests')
tests = tests.disable_auto_if(meson.is_subproject())
if get_option('tests') catch2_dep = dependency('catch2', required: tests)
subdir('tests') subdir('tests', if_found: catch2_dep)
endif

View File

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

View File

@@ -2,23 +2,11 @@
# meson.build here when generating the single file header so that it is placed # meson.build here when generating the single file header so that it is placed
# in the correct location. # in the correct location.
pymod = import('python')
prog_python = pymod.find_installation()
single_main_file = files('CLI11.hpp.in') single_main_file = files('CLI11.hpp.in')
single_header = custom_target(
if use_single_header 'CLI11.hpp',
single_header = custom_target( input: [cli11_headers, cli11_impl_headers],
'CLI11.hpp', output: 'CLI11.hpp',
input: [files('../scripts/MakeSingleHeader.py'), cli11_headers, cli11_impl_headers], command : [find_program('../scripts/MakeSingleHeader.py'), '@INPUT@', '--main', single_main_file, '--output', '@OUTPUT@'],
output: 'CLI11.hpp', depend_files: [single_main_file],
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

View File

@@ -5,19 +5,32 @@
// SPDX-License-Identifier: BSD-3-Clause // SPDX-License-Identifier: BSD-3-Clause
#include "app_helper.hpp" #include "app_helper.hpp"
#include <cmath>
#include <array> #include <array>
#include <cmath>
#include <complex> #include <complex>
#include <cstdint> #include <cstdint>
#include <cstdlib>
#include <limits> #include <limits>
#include <map>
#include <string> #include <string>
#include <tuple> #include <tuple>
#include <utility> #include <utility>
#include <vector> #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]") { TEST_CASE_METHOD(TApp, "OneFlagShort", "[app]") {
app.add_flag("-c,--count"); app.add_flag("-c,--count");
args = {"-c"}; args = {"-c"};
@@ -2979,38 +2992,107 @@ TEST_CASE("C20_compile", "simple") {
CHECK_FALSE(flag->empty()); 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 // #845
TEST_CASE("Ensure UTF-8", "[app]") { TEST_CASE("Ensure UTF-8", "[app]") {
const char *commandline = CLI11_ENSURE_UTF8_EXE " 1234 false \"hello world\""; auto retval = spawn_app_exe(PLATFORM_TEXT(CLI11_ENSURE_UTF8_EXE));
int retval = std::system(commandline);
if(retval == -1) { 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) { 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) { 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 // #845
TEST_CASE("Ensure UTF-8 called twice", "[app]") { TEST_CASE("Ensure UTF-8 called twice", "[app]") {
const char *commandline = CLI11_ENSURE_UTF8_TWICE_EXE " 1234 false \"hello world\""; auto retval = spawn_app_exe(PLATFORM_TEXT(CLI11_ENSURE_UTF8_TWICE_EXE));
int retval = std::system(commandline);
if(retval == -1) { 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) { 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) { 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", "//:cli11",
"@catch2", "@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) file(REMOVE ${CMAKE_BINARY_DIR}/test_atomic.cpp)
endif() endif()
find_package(Threads)
target_link_libraries(AppTest PRIVATE Threads::Threads)
# Add -Wno-deprecated-declarations to DeprecatedTest # Add -Wno-deprecated-declarations to DeprecatedTest
set(no-deprecated-declarations $<$<CXX_COMPILER_ID:MSVC>:/wd4996> set(no-deprecated-declarations $<$<CXX_COMPILER_ID:MSVC>:/wd4996>
$<$<NOT:$<CXX_COMPILER_ID:MSVC>>:-Wno-deprecated-declarations>) $<$<NOT:$<CXX_COMPILER_ID:MSVC>>:-Wno-deprecated-declarations>)

View File

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