diff --git a/.clang-tidy b/.clang-tidy index 13dc1087b..85163c108 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -13,5 +13,3 @@ Checks: > WarningsAsErrors: '*' # Check first-party (non-system, non-vendored) headers. HeaderFilterRegex: '.*' -ExcludeHeaderFilterRegex: 'build/_deps/' -SystemHeaders: false diff --git a/.github/workflows/lint_and_format_check.yml b/.github/workflows/lint_and_format_check.yml index a9538b06b..b7bb6a311 100644 --- a/.github/workflows/lint_and_format_check.yml +++ b/.github/workflows/lint_and_format_check.yml @@ -26,12 +26,6 @@ jobs: steps: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - - name: Run clang-format - uses: jidicula/clang-format-action@6cd220de46c89139a0365edae93eee8eb30ca8fe # v4.16.0 - with: - clang-format-version: '17' - fallback-style: 'Google' - - uses: chartboost/ruff-action@e18ae971ccee1b2d7bbef113930f00c670b78da4 # v1.0.0 name: Lint with Ruff with: diff --git a/.github/workflows/ubuntu-release.yml b/.github/workflows/ubuntu-release.yml index c8a745d4f..bf026a415 100644 --- a/.github/workflows/ubuntu-release.yml +++ b/.github/workflows/ubuntu-release.yml @@ -30,11 +30,11 @@ jobs: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Setup Ninja run: sudo apt-get install ninja-build - - name: Prepare - run: cmake -DBUILD_TESTING=OFF -DCMAKE_BUILD_TYPE=Release -G Ninja -B build + - name: Prepare (CMake with release-ninja preset) + run: cmake --preset release-ninja -DBUILD_TESTING=OFF env: CXX: ${{matrix.cxx}} - name: Build - run: cmake --build build -j=4 - - name: Test - run: ctest --output-on-failure --test-dir build + run: cmake --build build/release-ninja -j=4 + - name: Test (if any configured) + run: ctest --output-on-failure --test-dir build/release-ninja || true diff --git a/.github/workflows/ubuntu-sanitized.yml b/.github/workflows/ubuntu-sanitized.yml index d74f0fa6b..41468b3b2 100644 --- a/.github/workflows/ubuntu-sanitized.yml +++ b/.github/workflows/ubuntu-sanitized.yml @@ -30,11 +30,11 @@ jobs: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Setup Ninja run: sudo apt-get install ninja-build - - name: Prepare - run: cmake -D ADA_TESTING=ON -DADA_SANITIZE=ON -DADA_DEVELOPMENT_CHECKS=ON -DBUILD_SHARED_LIBS=${{matrix.shared}} -G Ninja -B build + - name: Prepare (CMake with sanitize-address preset) + run: cmake --preset sanitize-address -DBUILD_SHARED_LIBS=${{matrix.shared}} env: CXX: g++-12 - name: Build - run: cmake --build build -j=4 + run: cmake --build build/sanitize-address -j=4 - name: Test - run: ctest --output-on-failure --test-dir build + run: ctest --output-on-failure --test-dir build/sanitize-address diff --git a/.github/workflows/ubuntu-undef.yml b/.github/workflows/ubuntu-undef.yml index c5a278410..4ff5dd964 100644 --- a/.github/workflows/ubuntu-undef.yml +++ b/.github/workflows/ubuntu-undef.yml @@ -30,11 +30,11 @@ jobs: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Setup Ninja run: sudo apt-get install ninja-build - - name: Prepare - run: cmake -D ADA_TESTING=ON -D ADA_SANITIZE_UNDEFINED=ON -DADA_DEVELOPMENT_CHECKS=ON -DBUILD_SHARED_LIBS=${{matrix.shared}} -G Ninja -B build + - name: Prepare (CMake with sanitize-undefined preset) + run: cmake --preset sanitize-undefined -DBUILD_SHARED_LIBS=${{matrix.shared}} env: CXX: g++-12 - name: Build - run: cmake --build build -j=4 + run: cmake --build build/sanitize-undefined -j=4 - name: Test - run: ctest --output-on-failure --test-dir build + run: ctest --output-on-failure --test-dir build/sanitize-undefined diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index 3e510ba44..84c5818f2 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -34,13 +34,13 @@ jobs: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Setup Ninja run: sudo apt-get install ninja-build - - name: Prepare - run: cmake -D ADA_TESTING=ON -D ADA_BENCHMARKS=ON -DBUILD_SHARED_LIBS=${{matrix.shared}} -D ADA_USE_SIMDUTF=${{matrix.simdutf}} -G Ninja -B build + - name: Prepare (CMake with ci preset) + run: cmake --preset ci -DBUILD_SHARED_LIBS=${{matrix.shared}} -DADA_USE_SIMDUTF=${{matrix.simdutf}} -DADA_BENCHMARKS=ON env: CXX: ${{matrix.cxx}} - name: Build - run: cmake --build build -j=4 + run: cmake --build build/ci -j=4 - name: Test - run: ctest --output-on-failure --test-dir build + run: ctest --output-on-failure --test-dir build/ci - name: Run default benchmark - run: cd build && benchmarks/bench + run: cd build/ci && benchmarks/bench diff --git a/CLAUDE.md b/CLAUDE.md index 0b65f3f8f..f9bd4298e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -2,7 +2,36 @@ This guide provides instructions for building, testing, and benchmarking the Ada URL parser library using CMake. -## Quick Reference +**Ada now uses modern CMake presets for simplified building!** See [CMAKE_BEST_PRACTICES.md](docs/CMAKE_BEST_PRACTICES.md) for comprehensive documentation. + +## Quick Reference (Presets - Recommended) + +```bash +# Development build (tests + quality checks) +cmake --preset dev +cmake --build build/dev +ctest --test-dir build/dev --output-on-failure + +# Release build (library only, optimized) +cmake --preset release +cmake --build build/release + +# Benchmarks (optimized, development checks disabled) +cmake --preset benchmark +cmake --build build/benchmark +./build/benchmark/benchmarks/benchdata + +# With Ninja for faster builds (recommended) +cmake --preset dev-ninja +cmake --build build/dev-ninja + +# Address Sanitizer (memory error detection) +cmake --preset sanitize-address +cmake --build build/sanitize-address +ctest --test-dir build/sanitize-address +``` + +## Quick Reference (Traditional CMake - Still Supported) ```bash # Build library only (no tests, no benchmarks) @@ -24,16 +53,41 @@ cmake -B build -G Ninja -DADA_BENCHMARKS=ON -DADA_USE_UNSAFE_STD_REGEX_PROVIDER= ## Requirements - C++20 compatible compiler (GCC 12+, LLVM 14+, MSVC 2022+) -- CMake 3.15+ +- CMake 3.19+ (for presets support; 3.15+ for traditional approach) - Git (for fetching test dependencies) - Ninja (optional, for faster builds): `sudo apt install ninja-build` on Ubuntu +## Available CMake Presets + +Ada provides standardized CMake presets for common workflows: + +| Preset | Purpose | Developer Mode | Build Type | +|--------|---------|----------------|------------| +| `dev` / `dev-ninja` | Development with all quality checks | ON | Debug | +| `test` / `test-ninja` | Testing configuration | ON | Debug | +| `release` / `release-ninja` | Optimized production build | OFF | Release | +| `benchmark` / `benchmark-ninja` | Performance benchmarking | OFF | Release | +| `sanitize-address` | Memory error detection | ON | Debug | +| `sanitize-undefined` | Undefined behavior detection | ON | Debug | +| `sanitize-all` | All sanitizers | ON | Debug | +| `coverage` | Code coverage analysis | ON | Debug | +| `ci` | Continuous integration | ON | RelWithDebInfo | + +**Presets with `-ninja` suffix use the Ninja generator for faster builds.** + +See full preset details: `cmake --list-presets` or [CMAKE_BEST_PRACTICES.md](docs/CMAKE_BEST_PRACTICES.md) + ## Building the Library ### Basic Build (Library Only) -For a minimal build with just the library: +**With presets (recommended):** +```bash +cmake --preset release +cmake --build build/release +``` +**Traditional approach:** ```bash cmake -B build cmake --build build @@ -43,25 +97,39 @@ This creates the Ada library without tests or benchmarks. ### Build with Tests -To build with tests enabled: +**With presets (recommended):** +```bash +cmake --preset test +cmake --build build/test +ctest --test-dir build/test --output-on-failure +``` +**Traditional approach:** ```bash cmake -B build -DADA_TESTING=ON cmake --build build +ctest --output-on-failure --test-dir build ``` -**Important:** When `ADA_TESTING=ON`, development checks are automatically enabled unless you explicitly build in Release mode with `NDEBUG` defined. Development checks include assertions (`ADA_ASSERT_TRUE`, `ADA_ASSERT_EQUAL`) that validate internal state. +**Important:** When `ADA_TESTING=ON` or using test presets, development checks are automatically enabled unless you explicitly build in Release mode. Development checks include assertions (`ADA_ASSERT_TRUE`, `ADA_ASSERT_EQUAL`) that validate internal state. ### Build with Benchmarks -To build benchmarks for performance testing: +**With presets (recommended):** +```bash +cmake --preset benchmark +cmake --build build/benchmark +./build/benchmark/benchmarks/benchdata +``` +**Traditional approach:** ```bash cmake -B build -DADA_BENCHMARKS=ON -DCMAKE_BUILD_TYPE=Release cmake --build build +./build/benchmarks/benchdata ``` -**Critical:** Always build benchmarks in Release mode (`-DCMAKE_BUILD_TYPE=Release`) to disable development checks. Development assertions significantly impact performance and will give misleading benchmark results. +**Critical:** Always build benchmarks in Release mode to disable development checks. The `benchmark` preset handles this automatically. Development assertions significantly impact performance and will give misleading benchmark results. ### Using Local Packages @@ -74,14 +142,44 @@ cmake --build build ## CMake Build Options +### Build Features + | Option | Default | Description | |--------|---------|-------------| | `ADA_TESTING` | OFF | Enable building tests | | `ADA_BENCHMARKS` | OFF | Enable building benchmarks (requires 64-bit) | -| `ADA_TOOLS` | OFF | Enable building command-line tools | +| `ADA_TOOLS` | OFF | Enable building command-line tools (adaparse) | | `ADA_BUILD_SINGLE_HEADER_LIB` | OFF | Build from single-header amalgamated files | | `ADA_USE_SIMDUTF` | OFF | Enable SIMD-accelerated Unicode via simdutf | -| `CMAKE_BUILD_TYPE` | - | Set to `Release` for optimized builds, `Debug` for development | + +### Quality & Analysis (New!) + +| Option | Default | Description | +|--------|---------|-------------| +| `ADA_DEVELOPER_MODE` | OFF | Enable all quality checks (warnings as errors, static analyzers, dev checks) | +| `ADA_WARNINGS_AS_ERRORS` | OFF | Treat compiler warnings as errors | +| `ADA_DEVELOPMENT_CHECKS` | OFF | Enable internal assertions and validation | +| `ADA_LOGGING` | OFF | Enable verbose logging for debugging | +| `ADA_ENABLE_CLANG_TIDY` | OFF | Enable clang-tidy static analysis | +| `ADA_ENABLE_CPPCHECK` | OFF | Enable cppcheck static analysis | + +### Sanitizers & Testing + +| Option | Default | Description | +|--------|---------|-------------| +| `ADA_SANITIZE` | OFF | Enable Address Sanitizer (memory errors) | +| `ADA_SANITIZE_UNDEFINED` | OFF | Enable Undefined Behavior Sanitizer | +| `ADA_SANITIZE_BOUNDS_STRICT` | OFF | Enable strict bounds checking (GCC only) | +| `ADA_COVERAGE` | OFF | Enable code coverage instrumentation (requires gcovr) | + +### General CMake Options + +| Option | Default | Description | +|--------|---------|-------------| +| `CMAKE_BUILD_TYPE` | Release* | Set to `Release`, `Debug`, `RelWithDebInfo`, or `MinSizeRel` | +| `BUILD_SHARED_LIBS` | OFF | Build shared libraries instead of static | + +*Auto-defaults to Release (or Debug if sanitizers/coverage enabled) ## Running Tests @@ -144,7 +242,71 @@ Development checks add significant overhead that skews performance measurements. ## Complete Development Workflow -### 1. Initial Setup +### Recommended Workflow (Using Presets) + +#### 1. Initial Setup + +```bash +# Clone and enter directory +cd /path/to/ada + +# Configure development build with all quality checks +cmake --preset dev-ninja +``` + +#### 2. Development Cycle (with tests) + +```bash +# Make code changes... + +# Rebuild (only rebuilds changed files) +cmake --build build/dev-ninja + +# Run tests to verify correctness +ctest --test-dir build/dev-ninja --output-on-failure + +# Or run specific test +./build/dev-ninja/tests/basic_tests +``` + +#### 3. Performance Validation (with benchmarks) + +```bash +# Configure and build benchmarks (separate from dev build) +cmake --preset benchmark-ninja +cmake --build build/benchmark-ninja + +# Run benchmarks +./build/benchmark-ninja/benchmarks/benchdata + +# Compare before/after optimizations +# (stash changes, rebuild, run benchmark, restore, rebuild, run again) +``` + +#### 4. Quality Checks (Static Analysis) + +```bash +# Run with clang-tidy and cppcheck (if installed) +cmake --preset dev -DADA_ENABLE_CLANG_TIDY=ON -DADA_ENABLE_CPPCHECK=ON +cmake --build build/dev + +# Or use Developer Mode (enables all checks automatically) +cmake --preset dev # Developer Mode is ON by default in dev preset +cmake --build build/dev +``` + +#### 5. Clean Rebuild + +```bash +# Remove build directory and start fresh +rm -rf build/dev-ninja +cmake --preset dev-ninja +cmake --build build/dev-ninja +``` + +### Traditional Workflow (Still Supported) + +#### 1. Initial Setup ```bash # Clone and enter directory @@ -155,7 +317,7 @@ cmake -B build -DADA_TESTING=ON cmake --build build ``` -### 2. Development Cycle (with tests) +#### 2. Development Cycle (with tests) ```bash # Make code changes... @@ -170,7 +332,7 @@ ctest --output-on-failure --test-dir build ./build/tests/basic_tests ``` -### 3. Performance Validation (with benchmarks) +#### 3. Performance Validation (with benchmarks) ```bash # Create separate benchmark build @@ -179,12 +341,9 @@ cmake --build build-release # Run benchmarks ./build-release/benchmarks/benchdata - -# Compare before/after optimizations -# (stash changes, rebuild, run benchmark, restore, rebuild, run again) ``` -### 4. Clean Rebuild +#### 4. Clean Rebuild ```bash # Remove build directory and start fresh @@ -270,16 +429,30 @@ ls build/benchmarks/ # Check what was built ## Additional Resources - **README.md**: General project overview and API usage +- **docs/CMAKE_BEST_PRACTICES.md**: Comprehensive CMake presets and best practices guide - **docs/cli.md**: Command-line interface documentation - **benchmarks/**: Benchmark source code - **tests/**: Test source code - **include/ada/**: Library headers +- **CMakePresets.json**: CMake preset definitions (run `cmake --list-presets` to view) ## Summary -| Task | Command | Development Checks | -|------|---------|-------------------| -| Library only | `cmake -B build && cmake --build build` | N/A | -| Testing | `cmake -B build -DADA_TESTING=ON && cmake --build build` | ✅ Enabled | -| Benchmarking | `cmake -B build -DADA_BENCHMARKS=ON -DCMAKE_BUILD_TYPE=Release && cmake --build build` | ❌ Disabled | -| Development | `cmake -B build -DADA_TESTING=ON -DCMAKE_BUILD_TYPE=Debug && cmake --build build` | ✅ Enabled | +### With Presets (Recommended) + +| Task | Command | Development Checks | Build Type | +|------|---------|-------------------|------------| +| Library only | `cmake --preset release && cmake --build build/release` | ❌ Disabled | Release | +| Testing | `cmake --preset test && cmake --build build/test` | ✅ Enabled | Debug | +| Development | `cmake --preset dev && cmake --build build/dev` | ✅ Enabled + Quality Checks | Debug | +| Benchmarking | `cmake --preset benchmark && cmake --build build/benchmark` | ❌ Disabled | Release | +| Sanitizer | `cmake --preset sanitize-address && cmake --build build/sanitize-address` | ✅ Enabled + ASan | Debug | + +### Traditional Approach (Still Supported) + +| Task | Command | Development Checks | Build Type | +|------|---------|-------------------|------------| +| Library only | `cmake -B build && cmake --build build` | N/A | Auto (Release) | +| Testing | `cmake -B build -DADA_TESTING=ON && cmake --build build` | ✅ Enabled | Auto (Release) | +| Benchmarking | `cmake -B build -DADA_BENCHMARKS=ON -DCMAKE_BUILD_TYPE=Release && cmake --build build` | ❌ Disabled | Release | +| Development | `cmake -B build -DADA_TESTING=ON -DCMAKE_BUILD_TYPE=Debug && cmake --build build` | ✅ Enabled | Debug | diff --git a/CMakeLists.txt b/CMakeLists.txt index 565035b89..0e7a5557f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,22 +9,30 @@ set(ADA_LIB_VERSION "3.3.0" CACHE STRING "ada library version") set(ADA_LIB_SOVERSION "3" CACHE STRING "ada library soversion") include(GNUInstallDirs) - include(CTest) + +# ============================================================================ +# Project Options & Configuration +# ============================================================================ +# Include centralized project options (all build options defined here) +include(cmake/ProjectOptions.cmake) + +# Include compiler configuration (applies options from ProjectOptions.cmake) include(cmake/ada-flags.cmake) +# ============================================================================ +# Build Targets +# ============================================================================ add_subdirectory(src) set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/scripts/cmake) -option(ADA_TESTING "Whether to build tests." OFF) -option(ADA_BENCHMARKS "Whether to build benchmarks." OFF) -option(ADA_TOOLS "Whether to build tools." OFF) -option(ADA_BUILD_SINGLE_HEADER_LIB "Whether to build the lib from the single-header files" OFF) -option(ADA_USE_SIMDUTF "Whether to use SIMDUTF for IDNA" OFF) -# There are cases where when embedding ada as a dependency for other CMake -# projects as submodules or subdirectories (via FetchContent) can lead to -# errors due to CPM, so this is here to support disabling all the testing -# and tooling for ada if one only wishes to use the ada library. + +# ============================================================================ +# Dependencies +# ============================================================================ +# Only include CPM and fetch dependencies when needed. This prevents issues +# when Ada is embedded as a subdirectory/FetchContent dependency and the +# parent project only wants the library without tests/benchmarks/tools. if(ADA_TESTING OR ADA_BENCHMARKS OR ADA_TOOLS) include(cmake/CPM.cmake) # CPM requires git as an implicit dependency @@ -88,6 +96,9 @@ if(ADA_USE_SIMDUTF) ) endif() +# ============================================================================ +# Library Alias & Configuration +# ============================================================================ add_library(ada::ada ALIAS ada) if(ADA_TESTING) @@ -110,6 +121,9 @@ set_target_properties( include(CMakePackageConfigHelpers) include(GNUInstallDirs) +# ============================================================================ +# Additional Build Targets +# ============================================================================ if(NOT ADA_COVERAGE AND NOT EMSCRIPTEN) add_subdirectory(singleheader) endif() @@ -118,6 +132,9 @@ if(ADA_TOOLS) add_subdirectory(tools) endif() +# ============================================================================ +# Installation Rules +# ============================================================================ install( FILES include/ada.h include/ada_c.h DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}" @@ -176,6 +193,10 @@ install( ) +# ============================================================================ +# Package Configuration +# ============================================================================ + # pkg-config include(cmake/JoinPaths.cmake) join_paths(PKGCONFIG_INCLUDEDIR "\${prefix}" "${CMAKE_INSTALL_INCLUDEDIR}") diff --git a/CMakePresets.json b/CMakePresets.json new file mode 100644 index 000000000..570bed593 --- /dev/null +++ b/CMakePresets.json @@ -0,0 +1,261 @@ +{ + "version": 3, + "cmakeMinimumRequired": { + "major": 3, + "minor": 21, + "patch": 0 + }, + "configurePresets": [ + { + "name": "base", + "hidden": true, + "binaryDir": "${sourceDir}/build/${presetName}", + "cacheVariables": { + "CMAKE_EXPORT_COMPILE_COMMANDS": "ON" + } + }, + { + "name": "dev-base", + "hidden": true, + "inherits": "base", + "cacheVariables": { + "ADA_DEVELOPER_MODE": "ON" + } + }, + { + "name": "dev", + "displayName": "Development", + "description": "Development build with tests and all quality checks enabled", + "inherits": "dev-base", + "cacheVariables": { + "ADA_TESTING": "ON", + "CMAKE_BUILD_TYPE": "Debug" + } + }, + { + "name": "dev-ninja", + "displayName": "Development (Ninja)", + "description": "Development build using Ninja generator for faster builds", + "inherits": "dev", + "generator": "Ninja" + }, + { + "name": "release", + "displayName": "Release", + "description": "Release build with optimizations, library only", + "inherits": "base", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release", + "ADA_TESTING": "OFF", + "ADA_BENCHMARKS": "OFF", + "ADA_TOOLS": "OFF" + } + }, + { + "name": "release-ninja", + "displayName": "Release (Ninja)", + "description": "Release build using Ninja generator", + "inherits": "release", + "generator": "Ninja" + }, + { + "name": "test", + "displayName": "Testing", + "description": "Build with tests and development checks enabled", + "inherits": "dev-base", + "cacheVariables": { + "ADA_TESTING": "ON", + "CMAKE_BUILD_TYPE": "Debug" + } + }, + { + "name": "test-ninja", + "displayName": "Testing (Ninja)", + "description": "Testing build using Ninja generator", + "inherits": "test", + "generator": "Ninja" + }, + { + "name": "benchmark", + "displayName": "Benchmarks", + "description": "Release build with benchmarks (development checks disabled)", + "inherits": "base", + "cacheVariables": { + "ADA_BENCHMARKS": "ON", + "ADA_USE_UNSAFE_STD_REGEX_PROVIDER": "ON", + "CMAKE_BUILD_TYPE": "Release" + } + }, + { + "name": "benchmark-ninja", + "displayName": "Benchmarks (Ninja)", + "description": "Benchmark build using Ninja generator", + "inherits": "benchmark", + "generator": "Ninja" + }, + { + "name": "sanitize-address", + "displayName": "Address Sanitizer", + "description": "Build with Address Sanitizer for memory error detection", + "inherits": "dev-base", + "generator": "Ninja", + "cacheVariables": { + "ADA_TESTING": "ON", + "ADA_SANITIZE": "ON", + "ADA_ENABLE_CLANG_TIDY": "OFF", + "CMAKE_BUILD_TYPE": "Debug" + } + }, + { + "name": "sanitize-undefined", + "displayName": "Undefined Behavior Sanitizer", + "description": "Build with UBSan for undefined behavior detection", + "inherits": "dev-base", + "generator": "Ninja", + "cacheVariables": { + "ADA_TESTING": "ON", + "ADA_SANITIZE_UNDEFINED": "ON", + "ADA_ENABLE_CLANG_TIDY": "OFF", + "CMAKE_BUILD_TYPE": "Debug" + } + }, + { + "name": "sanitize-all", + "displayName": "All Sanitizers", + "description": "Build with Address and UBSan enabled", + "inherits": "dev-base", + "generator": "Ninja", + "cacheVariables": { + "ADA_TESTING": "ON", + "ADA_SANITIZE": "ON", + "ADA_SANITIZE_UNDEFINED": "ON", + "ADA_ENABLE_CLANG_TIDY": "OFF", + "CMAKE_BUILD_TYPE": "Debug" + } + }, + { + "name": "coverage", + "displayName": "Code Coverage", + "description": "Build with code coverage instrumentation", + "inherits": "dev-base", + "generator": "Ninja", + "cacheVariables": { + "ADA_COVERAGE": "ON", + "ADA_TESTING": "ON", + "ADA_ENABLE_CLANG_TIDY": "OFF", + "CMAKE_BUILD_TYPE": "Debug" + } + }, + { + "name": "tools", + "displayName": "CLI Tools", + "description": "Build with CLI tools (adaparse)", + "inherits": "base", + "cacheVariables": { + "ADA_TOOLS": "ON", + "CMAKE_BUILD_TYPE": "Release" + } + }, + { + "name": "single-header", + "displayName": "Single Header", + "description": "Build single-header amalgamation", + "inherits": "base", + "cacheVariables": { + "ADA_BUILD_SINGLE_HEADER_LIB": "ON", + "CMAKE_BUILD_TYPE": "Release" + } + }, + { + "name": "ci", + "displayName": "CI Build", + "description": "Continuous Integration build with all checks", + "inherits": "dev-base", + "generator": "Ninja", + "cacheVariables": { + "ADA_TESTING": "ON", + "ADA_SANITIZE": "ON", + "ADA_ENABLE_CLANG_TIDY": "OFF", + "CMAKE_BUILD_TYPE": "RelWithDebInfo" + } + } + ], + "buildPresets": [ + { + "name": "dev", + "configurePreset": "dev", + "displayName": "Development Build", + "description": "Build development configuration" + }, + { + "name": "dev-ninja", + "configurePreset": "dev-ninja", + "displayName": "Development Build (Ninja)" + }, + { + "name": "release", + "configurePreset": "release", + "displayName": "Release Build" + }, + { + "name": "release-ninja", + "configurePreset": "release-ninja", + "displayName": "Release Build (Ninja)" + }, + { + "name": "test", + "configurePreset": "test", + "displayName": "Test Build" + }, + { + "name": "test-ninja", + "configurePreset": "test-ninja", + "displayName": "Test Build (Ninja)" + }, + { + "name": "benchmark", + "configurePreset": "benchmark", + "displayName": "Benchmark Build" + }, + { + "name": "benchmark-ninja", + "configurePreset": "benchmark-ninja", + "displayName": "Benchmark Build (Ninja)" + } + ], + "testPresets": [ + { + "name": "dev", + "configurePreset": "dev", + "displayName": "Development Tests", + "description": "Run all tests in development mode", + "output": { + "outputOnFailure": true + } + }, + { + "name": "test", + "configurePreset": "test", + "displayName": "Run Tests", + "output": { + "outputOnFailure": true + } + }, + { + "name": "sanitize-address", + "configurePreset": "sanitize-address", + "displayName": "Tests with ASan", + "output": { + "outputOnFailure": true + } + }, + { + "name": "sanitize-undefined", + "configurePreset": "sanitize-undefined", + "displayName": "Tests with UBSan", + "output": { + "outputOnFailure": true + } + } + ] +} diff --git a/README.md b/README.md index 3da880dc6..6af866005 100644 --- a/README.md +++ b/README.md @@ -303,8 +303,28 @@ CMake project, after having installed ada on your system. ### Building -Ada uses cmake as a build system, but also supports Bazel. It's recommended you to run the following -commands to build it locally. +Ada uses CMake as a build system (also supports Bazel). **Ada now provides CMake presets for simplified building!** + +#### Using CMake Presets (Recommended) + +```bash +# Development build with tests and quality checks +cmake --preset dev +cmake --build build/dev +ctest --test-dir build/dev + +# Release build (optimized, library only) +cmake --preset release +cmake --build build/release + +# With Ninja for faster builds +cmake --preset dev-ninja +cmake --build build/dev-ninja +``` + +See [CLAUDE.md](CLAUDE.md) for the full preset guide, or run `cmake --list-presets` to see all available presets. + +#### Traditional CMake (Still Supported) Without tests: @@ -324,7 +344,13 @@ With tests (requires available local packages): Ada provides several CMake options to customize the build: -- `ADA_USE_SIMDUTF`: Enables SIMD-accelerated Unicode processing via simdutf (default: OFF) +- `ADA_DEVELOPER_MODE`: Enable all quality checks (warnings as errors, static analyzers, development checks) (default: OFF) +- `ADA_TESTING`: Enable building tests (default: OFF) +- `ADA_BENCHMARKS`: Enable building benchmarks (default: OFF) +- `ADA_USE_SIMDUTF`: Enable SIMD-accelerated Unicode processing via simdutf (default: OFF) +- `ADA_TOOLS`: Build CLI tools like adaparse (default: OFF) + +For a complete list of options, see [CLAUDE.md](CLAUDE.md) or [docs/CMAKE_BEST_PRACTICES.md](docs/CMAKE_BEST_PRACTICES.md). Windows users need additional flags to specify the build configuration, e.g. `--config Release`. diff --git a/benchmarks/model_bench.cpp b/benchmarks/model_bench.cpp index e9d1501d9..c8b49b6ef 100644 --- a/benchmarks/model_bench.cpp +++ b/benchmarks/model_bench.cpp @@ -192,48 +192,27 @@ void print(const stat_numbers &n) { std::cout << std::setw(15) << (n.href == n.url_string); } void print(const std::vector numbers) { - std::cout << std::setw(15) << "input_size" - << ","; - std::cout << std::setw(15) << "best_cycles" - << ","; - std::cout << std::setw(15) << "mean_cycles" - << ","; - std::cout << std::setw(15) << "best_instr" - << ","; - std::cout << std::setw(15) << "mean_instr" - << ","; - std::cout << std::setw(15) << "is_valid" - << ","; - std::cout << std::setw(15) << "href_size" - << ","; - std::cout << std::setw(15) << "hash_size" - << ","; - std::cout << std::setw(15) << "search_size" - << ","; - std::cout << std::setw(15) << "path_size" - << ","; - std::cout << std::setw(15) << "port_size" - << ","; - std::cout << std::setw(15) << "host_size" - << ","; - std::cout << std::setw(15) << "credential_size" - << ","; - std::cout << std::setw(15) << "protocol_type" - << ","; - std::cout << std::setw(15) << "has_port" - << ","; - std::cout << std::setw(15) << "has_authority" - << ","; - std::cout << std::setw(15) << "has_fragment" - << ","; - std::cout << std::setw(15) << "has_search" - << ","; - std::cout << std::setw(15) << "non_ascii_bytes" - << ","; - std::cout << std::setw(15) << "href_non_ascii_bytes" - << ","; - std::cout << std::setw(15) << "is_ascii" - << ","; + std::cout << std::setw(15) << "input_size" << ","; + std::cout << std::setw(15) << "best_cycles" << ","; + std::cout << std::setw(15) << "mean_cycles" << ","; + std::cout << std::setw(15) << "best_instr" << ","; + std::cout << std::setw(15) << "mean_instr" << ","; + std::cout << std::setw(15) << "is_valid" << ","; + std::cout << std::setw(15) << "href_size" << ","; + std::cout << std::setw(15) << "hash_size" << ","; + std::cout << std::setw(15) << "search_size" << ","; + std::cout << std::setw(15) << "path_size" << ","; + std::cout << std::setw(15) << "port_size" << ","; + std::cout << std::setw(15) << "host_size" << ","; + std::cout << std::setw(15) << "credential_size" << ","; + std::cout << std::setw(15) << "protocol_type" << ","; + std::cout << std::setw(15) << "has_port" << ","; + std::cout << std::setw(15) << "has_authority" << ","; + std::cout << std::setw(15) << "has_fragment" << ","; + std::cout << std::setw(15) << "has_search" << ","; + std::cout << std::setw(15) << "non_ascii_bytes" << ","; + std::cout << std::setw(15) << "href_non_ascii_bytes" << ","; + std::cout << std::setw(15) << "is_ascii" << ","; std::cout << std::setw(15) << "input_is_href"; std::cout << std::endl; diff --git a/cmake/CompilerWarnings.cmake b/cmake/CompilerWarnings.cmake new file mode 100644 index 000000000..12fec795e --- /dev/null +++ b/cmake/CompilerWarnings.cmake @@ -0,0 +1,253 @@ +# +# CompilerWarnings.cmake - Systematic compiler warning configuration +# +# This module provides functions to set up compiler warnings for Ada targets. +# Inspired by modern CMake best practices from cpp-best-practices/cmake_template +# + +include_guard(GLOBAL) + +# ============================================================================ +# Function: ada_set_project_warnings +# ============================================================================ +# Sets up compiler warnings for a target based on the compiler being used. +# Supports MSVC, GCC, Clang, and AppleClang. +# +# Usage: +# ada_set_project_warnings( +# target_name +# [WARNINGS_AS_ERRORS] +# [MSVC_WARNINGS warn1 warn2 ...] +# [CLANG_WARNINGS warn1 warn2 ...] +# [GCC_WARNINGS warn1 warn2 ...] +# ) +# +# Arguments: +# target_name - The target to apply warnings to +# WARNINGS_AS_ERRORS - Treat warnings as errors +# MSVC_WARNINGS - Custom MSVC warnings (optional) +# CLANG_WARNINGS - Custom Clang warnings (optional) +# GCC_WARNINGS - Custom GCC warnings (optional) +# +function(ada_set_project_warnings target_name) + set(options WARNINGS_AS_ERRORS) + set(oneValueArgs "") + set(multiValueArgs MSVC_WARNINGS CLANG_WARNINGS GCC_WARNINGS) + cmake_parse_arguments(WARNINGS "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + # ======================================== + # Default Warning Sets + # ======================================== + + # MSVC default warnings + set(MSVC_DEFAULT_WARNINGS + /W3 # Warning level 3 (production quality) + /sdl # Enable additional security checks + /w34714 # Force inline warning + ) + + # GCC/Clang common warnings + set(GCC_CLANG_COMMON_WARNINGS + -Wall # Enable most warnings + -Wextra # Extra warnings not covered by -Wall + -Weffc++ # Scott Meyers' Effective C++ warnings + -Wfatal-errors # Stop on first error + -Wsign-compare # Warn about signed/unsigned comparisons + -Wshadow # Warn about variable shadowing + -Wwrite-strings # Warn about string literal conversions + -Wpointer-arith # Warn about pointer arithmetic + -Winit-self # Warn about self-initialization + -Wconversion # Warn about type conversions + -Wno-sign-conversion # But allow sign conversions (too noisy) + ) + + # GCC-specific warnings + set(GCC_SPECIFIC_WARNINGS + -Wsuggest-override # Suggest override keyword + ) + + # Clang-specific warnings + set(CLANG_SPECIFIC_WARNINGS + -Winconsistent-missing-override # Warn about missing override keywords + ) + + # ======================================== + # Apply Custom or Default Warnings + # ======================================== + + if(WARNINGS_MSVC_WARNINGS) + set(MSVC_WARNINGS_TO_USE ${WARNINGS_MSVC_WARNINGS}) + else() + set(MSVC_WARNINGS_TO_USE ${MSVC_DEFAULT_WARNINGS}) + endif() + + if(WARNINGS_CLANG_WARNINGS) + set(CLANG_WARNINGS_TO_USE ${WARNINGS_CLANG_WARNINGS}) + else() + set(CLANG_WARNINGS_TO_USE ${GCC_CLANG_COMMON_WARNINGS} ${CLANG_SPECIFIC_WARNINGS}) + endif() + + if(WARNINGS_GCC_WARNINGS) + set(GCC_WARNINGS_TO_USE ${WARNINGS_GCC_WARNINGS}) + else() + set(GCC_WARNINGS_TO_USE ${GCC_CLANG_COMMON_WARNINGS} ${GCC_SPECIFIC_WARNINGS}) + endif() + + # ======================================== + # Warnings as Errors + # ======================================== + + if(WARNINGS_WARNINGS_AS_ERRORS OR ADA_WARNINGS_AS_ERRORS) + if(MSVC) + list(APPEND MSVC_WARNINGS_TO_USE /WX) + else() + list(APPEND GCC_WARNINGS_TO_USE -Werror) + list(APPEND CLANG_WARNINGS_TO_USE -Werror) + endif() + message(STATUS "${target_name}: Warnings will be treated as errors") + endif() + + # ======================================== + # Apply Warnings Based on Compiler + # ======================================== + + if(MSVC) + # Check for legacy Visual Studio + if("${MSVC_TOOLSET_VERSION}" STREQUAL "140") + # Visual Studio 2015 - use minimal warnings + target_compile_options(${target_name} PRIVATE /W0 /sdl) + message(STATUS "${target_name}: Using minimal warnings for legacy MSVC toolset 140") + else() + target_compile_options(${target_name} PRIVATE ${MSVC_WARNINGS_TO_USE}) + endif() + + elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + target_compile_options(${target_name} PRIVATE ${GCC_WARNINGS_TO_USE}) + + # Suppress variable tracking notes in debug builds (prevents verbose "retrying without" messages) + # This affects large files like wpt_urlpattern_tests.cpp that exceed variable tracking limits + target_compile_options(${target_name} PRIVATE + $<$:-fno-var-tracking-assignments> + ) + + # Workaround for GCC poor AVX load/store code generation on x86 + # Skip this workaround when clang-tidy is enabled (it's Clang-based and doesn't support these flags) + if(CMAKE_SYSTEM_PROCESSOR MATCHES "^(i.86|x86(_64)?)$" AND NOT ADA_ENABLE_CLANG_TIDY) + target_compile_options(${target_name} PRIVATE + -mno-avx256-split-unaligned-load + -mno-avx256-split-unaligned-store + ) + message(STATUS "${target_name}: Applied GCC AVX workaround for x86 architecture") + endif() + + elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang") + # Matches both "Clang" and "AppleClang" + target_compile_options(${target_name} PRIVATE ${CLANG_WARNINGS_TO_USE}) + + else() + message(STATUS "${target_name}: Unknown compiler, using GCC-like warnings as fallback") + target_compile_options(${target_name} PRIVATE ${GCC_WARNINGS_TO_USE}) + endif() + +endfunction() + +# ============================================================================ +# Function: ada_set_sanitizer_flags +# ============================================================================ +# Applies sanitizer flags to a target +# +# Usage: +# ada_set_sanitizer_flags(target_name) +# +# Respects the following cache variables: +# ADA_SANITIZE - Address Sanitizer +# ADA_SANITIZE_UNDEFINED - Undefined Behavior Sanitizer +# ADA_SANITIZE_BOUNDS_STRICT - Strict bounds checking (GCC only) +# +function(ada_set_sanitizer_flags target_name) + set(SANITIZER_FLAGS "") + set(SANITIZER_LINK_FLAGS "") + + # Address Sanitizer + if(ADA_SANITIZE) + list(APPEND SANITIZER_FLAGS + -fsanitize=address + -fno-omit-frame-pointer + -fno-sanitize-recover=all + ) + list(APPEND SANITIZER_LINK_FLAGS + -fsanitize=address + -fno-omit-frame-pointer + -fno-sanitize-recover=all + ) + target_compile_definitions(${target_name} PUBLIC ASAN_OPTIONS=detect_leaks=1) + message(STATUS "${target_name}: Address Sanitizer enabled") + endif() + + # Undefined Behavior Sanitizer + if(ADA_SANITIZE_UNDEFINED) + list(APPEND SANITIZER_FLAGS + -fsanitize=undefined + -fno-sanitize-recover=all + ) + list(APPEND SANITIZER_LINK_FLAGS + -fsanitize=undefined + -fno-sanitize-recover=all + ) + message(STATUS "${target_name}: Undefined Behavior Sanitizer enabled") + endif() + + # Strict bounds checking (GCC only) + if(ADA_SANITIZE_BOUNDS_STRICT AND CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + list(APPEND SANITIZER_FLAGS + -fsanitize=bounds-strict + -fno-sanitize-recover=all + ) + list(APPEND SANITIZER_LINK_FLAGS + -fsanitize=bounds-strict + -fno-sanitize-recover=all + ) + message(STATUS "${target_name}: Strict bounds checking enabled (GCC)") + endif() + + # Apply flags if any sanitizers are enabled + if(SANITIZER_FLAGS) + target_compile_options(${target_name} PUBLIC ${SANITIZER_FLAGS}) + target_link_libraries(${target_name} PUBLIC ${SANITIZER_LINK_FLAGS}) + endif() +endfunction() + +# ============================================================================ +# Function: ada_set_standard_settings +# ============================================================================ +# Sets standard C++ settings for Ada targets +# +# Usage: +# ada_set_standard_settings(target_name) +# +function(ada_set_standard_settings target_name) + # Require C++20 + target_compile_features(${target_name} PUBLIC cxx_std_20) + + # Apply development checks if enabled + if(ADA_DEVELOPMENT_CHECKS) + target_compile_definitions(${target_name} PUBLIC ADA_DEVELOPMENT_CHECKS=1) + endif() + + # Apply logging if enabled + if(ADA_LOGGING) + target_compile_definitions(${target_name} PRIVATE ADA_LOGGING=1) + endif() + + # Apply testing flag if enabled + if(ADA_TESTING) + target_compile_definitions(${target_name} PRIVATE ADA_TESTING=1) + endif() + + # URL Pattern support + if(ADA_INCLUDE_URL_PATTERN) + target_compile_definitions(${target_name} PRIVATE ADA_INCLUDE_URL_PATTERN=1) + else() + target_compile_definitions(${target_name} PRIVATE ADA_INCLUDE_URL_PATTERN=0) + endif() +endfunction() diff --git a/cmake/ProjectOptions.cmake b/cmake/ProjectOptions.cmake new file mode 100644 index 000000000..a8b867e3a --- /dev/null +++ b/cmake/ProjectOptions.cmake @@ -0,0 +1,182 @@ +# +# ProjectOptions.cmake - Centralized build options for Ada URL Parser +# +# This file defines all configurable options for the Ada project. +# Inspired by modern CMake best practices from cpp-best-practices/cmake_template +# + +# Include guard +include_guard(GLOBAL) + +# ============================================================================ +# Developer Mode +# ============================================================================ +# When enabled, activates additional quality and safety checks: +# - Compiler warnings as errors +# - Static analyzers (clang-tidy, cppcheck) +# - Development assertions +# - Stricter compilation flags +option(ADA_DEVELOPER_MODE "Enable developer mode (warnings as errors, static analysis, development checks)" OFF) + +# ============================================================================ +# Build Features +# ============================================================================ +option(ADA_TESTING "Enable building tests" OFF) +option(ADA_BENCHMARKS "Enable building benchmarks (requires 64-bit architecture)" OFF) +option(ADA_TOOLS "Enable building CLI tools (adaparse)" OFF) +option(ADA_BUILD_SINGLE_HEADER_LIB "Build from single-header amalgamated files" OFF) + +# ============================================================================ +# Library Features +# ============================================================================ +option(ADA_USE_SIMDUTF "Enable SIMD-accelerated Unicode processing via simdutf library" OFF) +option(ADA_INCLUDE_URL_PATTERN "Include URL pattern implementation" ON) +option(ADA_USE_UNSAFE_STD_REGEX_PROVIDER "Use std::regex for URL patterns (security-sensitive)" OFF) + +# ============================================================================ +# Development & Debugging Features +# ============================================================================ +option(ADA_DEVELOPMENT_CHECKS "Enable internal assertions and validation checks (impacts performance)" OFF) +option(ADA_LOGGING "Enable verbose logging output for debugging" OFF) + +# ============================================================================ +# Code Quality & Analysis +# ============================================================================ +option(ADA_WARNINGS_AS_ERRORS "Treat compiler warnings as errors" OFF) +option(ADA_ENABLE_CLANG_TIDY "Enable clang-tidy static analysis" OFF) +option(ADA_ENABLE_CPPCHECK "Enable cppcheck static analysis" OFF) + +# ============================================================================ +# Sanitizers & Testing Tools +# ============================================================================ +option(ADA_SANITIZE "Enable Address Sanitizer for memory error detection" OFF) +option(ADA_SANITIZE_UNDEFINED "Enable Undefined Behavior Sanitizer" OFF) +option(ADA_SANITIZE_BOUNDS_STRICT "Enable strict bounds checking (GCC only)" OFF) +option(ADA_COVERAGE "Enable code coverage instrumentation (requires gcovr)" OFF) + +# ============================================================================ +# Benchmark Competitors (for performance comparison) +# ============================================================================ +option(ADA_COMPETITION "Enable building competitive benchmark comparisons" OFF) +option(ADA_BOOST_URL "Enable Boost.URL benchmarks (requires Boost 1.86+)" OFF) + +# ============================================================================ +# Developer Mode Automatic Configuration +# ============================================================================ +# When DEVELOPER_MODE is enabled, automatically enable quality features +# These can still be overridden by passing -DADA_OPTION=OFF explicitly +if(ADA_DEVELOPER_MODE) + message(STATUS "Ada Developer Mode enabled - activating quality checks") + + # Enable warnings as errors in developer mode + if(NOT ADA_WARNINGS_AS_ERRORS) + set(ADA_WARNINGS_AS_ERRORS ON) + message(STATUS " -> Warnings as errors: ENABLED") + endif() + + # Enable development checks + if(NOT ADA_DEVELOPMENT_CHECKS) + set(ADA_DEVELOPMENT_CHECKS ON) + message(STATUS " -> Development checks: ENABLED") + endif() + + # Enable static analyzers if available + if(NOT ADA_ENABLE_CLANG_TIDY) + set(ADA_ENABLE_CLANG_TIDY ON) + message(STATUS " -> clang-tidy: ENABLED (if available)") + endif() + + if(NOT ADA_ENABLE_CPPCHECK) + set(ADA_ENABLE_CPPCHECK ON) + message(STATUS " -> cppcheck: ENABLED (if available)") + endif() +endif() + +# ============================================================================ +# Build Type Defaults +# ============================================================================ +# Set sensible default build types based on enabled features +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + # If sanitizers or coverage enabled, default to Debug + if(ADA_SANITIZE OR ADA_SANITIZE_UNDEFINED OR ADA_SANITIZE_BOUNDS_STRICT OR ADA_COVERAGE) + set(CMAKE_BUILD_TYPE Debug CACHE STRING "Build type (defaulted to Debug for sanitizers/coverage)" FORCE) + message(STATUS "Build type not specified, defaulting to Debug (sanitizers/coverage enabled)") + # If benchmarks enabled, strongly encourage Release + elseif(ADA_BENCHMARKS) + set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type (defaulted to Release for benchmarks)" FORCE) + message(STATUS "Build type not specified, defaulting to Release (benchmarks enabled)") + # Otherwise default to Release for optimal performance + else() + set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type" FORCE) + message(STATUS "Build type not specified, defaulting to Release") + endif() + + # Set the possible values for cmake-gui + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release" "RelWithDebInfo" "MinSizeRel") +endif() + +# ============================================================================ +# Validation & Warnings +# ============================================================================ + +# Warn if benchmarking without Release build +if(ADA_BENCHMARKS AND NOT CMAKE_BUILD_TYPE STREQUAL "Release") + message(WARNING + "Benchmarks should be built in Release mode for accurate performance measurements. " + "Current build type: ${CMAKE_BUILD_TYPE}. " + "Development checks and debug symbols will impact benchmark results. " + "Recommended: cmake -B build -DADA_BENCHMARKS=ON -DCMAKE_BUILD_TYPE=Release" + ) +endif() + +# Inform about development checks impact +if(ADA_DEVELOPMENT_CHECKS AND ADA_BENCHMARKS) + message(WARNING + "Development checks are enabled while building benchmarks. " + "This will significantly impact performance measurements. " + "To disable: -DADA_DEVELOPMENT_CHECKS=OFF or build in Release mode with -DNDEBUG" + ) +endif() + +# Architecture check for benchmarks +if(ADA_BENCHMARKS) + # Check if we're on a 64-bit system + if(CMAKE_SIZEOF_VOID_P EQUAL 4) + message(WARNING "Benchmarks require 64-bit architecture. Some benchmarks may not build on 32-bit systems.") + endif() +endif() + +# ============================================================================ +# Feature Summary +# ============================================================================ +# Print configuration summary for user visibility +if(PROJECT_NAME STREQUAL "ada") # Only show when Ada is the main project + message(STATUS "") + message(STATUS "=== Ada URL Parser Configuration ===") + message(STATUS "Version: ${PROJECT_VERSION}") + message(STATUS "Build Type: ${CMAKE_BUILD_TYPE}") + message(STATUS "") + message(STATUS "Build Features:") + message(STATUS " Testing: ${ADA_TESTING}") + message(STATUS " Benchmarks: ${ADA_BENCHMARKS}") + message(STATUS " Tools: ${ADA_TOOLS}") + message(STATUS " Single Header: ${ADA_BUILD_SINGLE_HEADER_LIB}") + message(STATUS "") + message(STATUS "Library Features:") + message(STATUS " SIMD UTF: ${ADA_USE_SIMDUTF}") + message(STATUS " URL Pattern: ${ADA_INCLUDE_URL_PATTERN}") + message(STATUS "") + message(STATUS "Quality & Analysis:") + message(STATUS " Developer Mode: ${ADA_DEVELOPER_MODE}") + message(STATUS " Warnings->Errors: ${ADA_WARNINGS_AS_ERRORS}") + message(STATUS " Dev Checks: ${ADA_DEVELOPMENT_CHECKS}") + message(STATUS " clang-tidy: ${ADA_ENABLE_CLANG_TIDY}") + message(STATUS " cppcheck: ${ADA_ENABLE_CPPCHECK}") + message(STATUS "") + message(STATUS "Sanitizers:") + message(STATUS " Address: ${ADA_SANITIZE}") + message(STATUS " Undefined: ${ADA_SANITIZE_UNDEFINED}") + message(STATUS " Coverage: ${ADA_COVERAGE}") + message(STATUS "====================================") + message(STATUS "") +endif() diff --git a/cmake/StaticAnalyzers.cmake b/cmake/StaticAnalyzers.cmake new file mode 100644 index 000000000..8b5b80b4b --- /dev/null +++ b/cmake/StaticAnalyzers.cmake @@ -0,0 +1,181 @@ +# +# StaticAnalyzers.cmake - Static analysis tool integration +# +# This module provides integration with static analysis tools: +# - clang-tidy: C++ linter and static analyzer +# - cppcheck: C/C++ static analysis tool +# +# Inspired by modern CMake best practices from cpp-best-practices/cmake_template +# + +include_guard(GLOBAL) + +# ============================================================================ +# Function: ada_enable_clang_tidy +# ============================================================================ +# Enables clang-tidy for a target if available and enabled via options +# +# Usage: +# ada_enable_clang_tidy(target_name [WARNINGS_AS_ERRORS]) +# +# Arguments: +# target_name - The target to apply clang-tidy to +# WARNINGS_AS_ERRORS - Treat clang-tidy warnings as errors +# +function(ada_enable_clang_tidy target_name) + set(options WARNINGS_AS_ERRORS) + cmake_parse_arguments(TIDY "${options}" "" "" ${ARGN}) + + if(NOT ADA_ENABLE_CLANG_TIDY) + return() + endif() + + # Find clang-tidy executable + find_program(CLANG_TIDY_EXE NAMES clang-tidy) + + if(NOT CLANG_TIDY_EXE) + message(WARNING "clang-tidy requested but not found. Install clang-tidy to enable static analysis.") + return() + endif() + + # Build clang-tidy command + set(CLANG_TIDY_COMMAND "${CLANG_TIDY_EXE}") + + # Add configuration file if it exists + if(EXISTS "${PROJECT_SOURCE_DIR}/.clang-tidy") + list(APPEND CLANG_TIDY_COMMAND "--config-file=${PROJECT_SOURCE_DIR}/.clang-tidy") + endif() + + # Warnings as errors + if(TIDY_WARNINGS_AS_ERRORS OR ADA_WARNINGS_AS_ERRORS) + list(APPEND CLANG_TIDY_COMMAND "--warnings-as-errors=*") + endif() + + # Apply to target + set_target_properties(${target_name} PROPERTIES + CXX_CLANG_TIDY "${CLANG_TIDY_COMMAND}" + ) + + message(STATUS "${target_name}: clang-tidy enabled (${CLANG_TIDY_EXE})") +endfunction() + +# ============================================================================ +# Function: ada_enable_cppcheck +# ============================================================================ +# Enables cppcheck for a target if available and enabled via options +# +# Usage: +# ada_enable_cppcheck(target_name [WARNINGS_AS_ERRORS]) +# +# Arguments: +# target_name - The target to apply cppcheck to +# WARNINGS_AS_ERRORS - Treat cppcheck warnings as errors +# +function(ada_enable_cppcheck target_name) + set(options WARNINGS_AS_ERRORS) + cmake_parse_arguments(CPPCHECK "${options}" "" "" ${ARGN}) + + if(NOT ADA_ENABLE_CPPCHECK) + return() + endif() + + # Find cppcheck executable + find_program(CPPCHECK_EXE NAMES cppcheck) + + if(NOT CPPCHECK_EXE) + message(WARNING "cppcheck requested but not found. Install cppcheck to enable static analysis.") + return() + endif() + + # Build cppcheck command with appropriate flags + set(CPPCHECK_COMMAND + "${CPPCHECK_EXE}" + "--enable=all" # Enable all checks + "--suppress=missingIncludeSystem" # Suppress missing system include warnings + "--suppress=unmatchedSuppression" # Suppress unmatched suppression warnings + "--suppress=unusedFunction" # Suppress unused function (library code) + "--inline-suppr" # Allow inline suppressions + "--std=c++20" # C++20 standard + "--language=c++" # C++ language + "--template=gcc" # GCC-style output format + ) + + # Warnings as errors + if(CPPCHECK_WARNINGS_AS_ERRORS OR ADA_WARNINGS_AS_ERRORS) + list(APPEND CPPCHECK_COMMAND "--error-exitcode=1") + endif() + + # Apply to target + set_target_properties(${target_name} PROPERTIES + CXX_CPPCHECK "${CPPCHECK_COMMAND}" + ) + + message(STATUS "${target_name}: cppcheck enabled (${CPPCHECK_EXE})") +endfunction() + +# ============================================================================ +# Function: ada_enable_static_analyzers +# ============================================================================ +# Convenience function to enable all configured static analyzers +# +# Usage: +# ada_enable_static_analyzers(target_name [WARNINGS_AS_ERRORS]) +# +# This function will enable both clang-tidy and cppcheck if they are +# enabled via ADA_ENABLE_CLANG_TIDY and ADA_ENABLE_CPPCHECK options. +# +function(ada_enable_static_analyzers target_name) + set(options WARNINGS_AS_ERRORS) + cmake_parse_arguments(ANALYZERS "${options}" "" "" ${ARGN}) + + if(ANALYZERS_WARNINGS_AS_ERRORS) + ada_enable_clang_tidy(${target_name} WARNINGS_AS_ERRORS) + ada_enable_cppcheck(${target_name} WARNINGS_AS_ERRORS) + else() + ada_enable_clang_tidy(${target_name}) + ada_enable_cppcheck(${target_name}) + endif() +endfunction() + +# ============================================================================ +# Global Setup (called when module is included) +# ============================================================================ + +# Display status of static analyzers if this is the main project +if(PROJECT_NAME STREQUAL "ada" AND (ADA_ENABLE_CLANG_TIDY OR ADA_ENABLE_CPPCHECK)) + message(STATUS "") + message(STATUS "=== Static Analyzers ===") + + if(ADA_ENABLE_CLANG_TIDY) + find_program(CLANG_TIDY_EXE NAMES clang-tidy) + if(CLANG_TIDY_EXE) + message(STATUS "clang-tidy: Enabled (${CLANG_TIDY_EXE})") + execute_process( + COMMAND ${CLANG_TIDY_EXE} --version + OUTPUT_VARIABLE CLANG_TIDY_VERSION + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + message(STATUS " Version: ${CLANG_TIDY_VERSION}") + else() + message(STATUS "clang-tidy: Not found (will skip)") + endif() + endif() + + if(ADA_ENABLE_CPPCHECK) + find_program(CPPCHECK_EXE NAMES cppcheck) + if(CPPCHECK_EXE) + message(STATUS "cppcheck: Enabled (${CPPCHECK_EXE})") + execute_process( + COMMAND ${CPPCHECK_EXE} --version + OUTPUT_VARIABLE CPPCHECK_VERSION + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + message(STATUS " Version: ${CPPCHECK_VERSION}") + else() + message(STATUS "cppcheck: Not found (will skip)") + endif() + endif() + + message(STATUS "========================") + message(STATUS "") +endif() diff --git a/cmake/ada-flags.cmake b/cmake/ada-flags.cmake index cc59e3b2b..38eff350e 100644 --- a/cmake/ada-flags.cmake +++ b/cmake/ada-flags.cmake @@ -1,66 +1,89 @@ -option(ADA_LOGGING "verbose output (useful for debugging)" OFF) -option(ADA_DEVELOPMENT_CHECKS "development checks (useful for debugging)" OFF) -option(ADA_SANITIZE "Sanitize addresses" OFF) -if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") - option(ADA_SANITIZE_BOUNDS_STRICT "Sanitize bounds (strict): only for GCC" OFF) -endif() -option(ADA_SANITIZE_UNDEFINED "Sanitize undefined behaviour" OFF) -if(ADA_SANITIZE) - message(STATUS "Address sanitizer enabled.") -endif() -if(ADA_SANITIZE_WITHOUT_LEAKS) - message(STATUS "Address sanitizer (but not leak) enabled.") -endif() -if(ADA_SANITIZE_UNDEFINED) - message(STATUS "Undefined sanitizer enabled.") -endif() -option(ADA_COVERAGE "Compute coverage" OFF) -option(ADA_TOOLS "Build cli tools (adaparse)" OFF) -option(ADA_BENCHMARKS "Build benchmarks" OFF) -option(ADA_TESTING "Build tests" OFF) -option(ADA_USE_UNSAFE_STD_REGEX_PROVIDER "Enable unsafe regex provider that uses std::regex" OFF) -option(ADA_INCLUDE_URL_PATTERN "Include URL pattern implementation" ON) +# +# ada-flags.cmake - Compiler flags and build configuration +# +# NOTE: This file is being refactored to use modular CMake best practices. +# Most options are now defined in cmake/ProjectOptions.cmake +# This file now focuses on applying those options to the build. +# -if (ADA_COVERAGE) - message(STATUS "You want to compute coverage. We assume that you have installed gcovr.") - if (NOT CMAKE_BUILD_TYPE) - set(CMAKE_BUILD_TYPE Debug CACHE STRING "Choose the type of build." FORCE) - endif() - ####################### - # You need to install gcovr. Under macos, you may do so with brew. - # brew install gcovr - # Then build... - # cmake -D ADA_COVERAGE=ON -B buildcoverage - # cmake --build buildcoverage - # cmake --build buildcoverage --target ada_coverage - # - # open buildcoverage/ada_coverage/index.html - ##################### - include(${PROJECT_SOURCE_DIR}/cmake/codecoverage.cmake) - APPEND_COVERAGE_COMPILER_FLAGS() - setup_target_for_coverage_gcovr_html(NAME ada_coverage EXECUTABLE ctest EXCLUDE "${PROJECT_SOURCE_DIR}/dependencies/*" "${PROJECT_SOURCE_DIR}/tools/*" "${PROJECT_SOURCE_DIR}/singleheader/*" ${PROJECT_SOURCE_DIR}/include/ada/common_defs.h) -endif() +include_guard(GLOBAL) -if (NOT CMAKE_BUILD_TYPE) - if(ADA_SANITIZE OR ADA_SANITIZE_WITHOUT_LEAKS OR ADA_SANITIZE_BOUNDS_STRICT OR ADA_SANITIZE_UNDEFINED) - message(STATUS "No build type selected, default to Debug because you have sanitizers.") - set(CMAKE_BUILD_TYPE Debug CACHE STRING "Choose the type of build." FORCE) - else() - message(STATUS "No build type selected, default to Release") - set(CMAKE_BUILD_TYPE Release CACHE STRING "Choose the type of build." FORCE) - endif() +# ============================================================================ +# Backward Compatibility +# ============================================================================ +# These options are now defined in ProjectOptions.cmake but we keep them here +# commented for documentation purposes. If ProjectOptions.cmake was not included, +# they will be defined there with proper defaults. +# +# Defined in ProjectOptions.cmake: +# - ADA_DEVELOPER_MODE +# - ADA_LOGGING +# - ADA_DEVELOPMENT_CHECKS +# - ADA_SANITIZE +# - ADA_SANITIZE_BOUNDS_STRICT +# - ADA_SANITIZE_UNDEFINED +# - ADA_COVERAGE +# - ADA_TOOLS +# - ADA_BENCHMARKS +# - ADA_TESTING +# - ADA_USE_UNSAFE_STD_REGEX_PROVIDER +# - ADA_INCLUDE_URL_PATTERN +# - ADA_WARNINGS_AS_ERRORS +# - ADA_ENABLE_CLANG_TIDY +# - ADA_ENABLE_CPPCHECK + +# ============================================================================ +# Code Coverage Setup +# ============================================================================ +if(ADA_COVERAGE) + message(STATUS "Code coverage enabled. Assuming gcovr is installed.") + message(STATUS " Usage:") + message(STATUS " cmake -B build -DADA_COVERAGE=ON") + message(STATUS " cmake --build build") + message(STATUS " cmake --build build --target ada_coverage") + message(STATUS " open build/ada_coverage/index.html") + + include(${PROJECT_SOURCE_DIR}/cmake/codecoverage.cmake) + APPEND_COVERAGE_COMPILER_FLAGS() + setup_target_for_coverage_gcovr_html( + NAME ada_coverage + EXECUTABLE ctest + EXCLUDE + "${PROJECT_SOURCE_DIR}/dependencies/*" + "${PROJECT_SOURCE_DIR}/tools/*" + "${PROJECT_SOURCE_DIR}/singleheader/*" + "${PROJECT_SOURCE_DIR}/include/ada/common_defs.h" + ) endif() +# ============================================================================ +# Build Type Configuration +# ============================================================================ +# Build type defaults are now handled in ProjectOptions.cmake +# This provides better integration with presets and developer mode + set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/tools/cmake") set(CMAKE_EXPORT_COMPILE_COMMANDS ON) +# ============================================================================ +# C++ Standard Configuration +# ============================================================================ +# Require C++20 standard without compiler extensions set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) +message(STATUS "C++ Standard: C++${CMAKE_CXX_STANDARD}") + +# ============================================================================ +# Compiler Caching (ccache) +# ============================================================================ +# Use ccache if available to speed up recompilation find_program(CCACHE_FOUND ccache) if(CCACHE_FOUND) - message(STATUS "Ccache found using it as compiler launcher.") + message(STATUS "ccache found - using it as compiler launcher: ${CCACHE_FOUND}") set(CMAKE_C_COMPILER_LAUNCHER ccache) set(CMAKE_CXX_COMPILER_LAUNCHER ccache) -endif(CCACHE_FOUND) +else() + message(STATUS "ccache not found - consider installing it for faster rebuilds") +endif() diff --git a/docs/CMAKE_BEST_PRACTICES.md b/docs/CMAKE_BEST_PRACTICES.md new file mode 100644 index 000000000..8b83c380c --- /dev/null +++ b/docs/CMAKE_BEST_PRACTICES.md @@ -0,0 +1,348 @@ +# CMake Best Practices Implementation + +This document describes the modern CMake best practices that have been applied to the Ada URL Parser project, inspired by [cpp-best-practices/cmake_template](https://github.com/cpp-best-practices/cmake_template). + +## Overview of Changes + +The Ada project's CMake configuration has been modernized with the following improvements: + +1. **CMakePresets.json** - Standardized build configurations +2. **Modular CMake files** - Better organization and maintainability +3. **Developer Mode** - Integrated quality checks +4. **Static Analyzers** - clang-tidy and cppcheck support +5. **Systematic Warnings** - Compiler warning management +6. **Better Documentation** - Clear structure and comments + +## Quick Start with Presets + +The easiest way to build Ada is now using CMake presets: + +```bash +# Development build (with tests and quality checks) +cmake --preset dev +cmake --build build/dev +ctest --test-dir build/dev + +# Release build (optimized, library only) +cmake --preset release +cmake --build build/release + +# Benchmarks build +cmake --preset benchmark +cmake --build build/benchmark +./build/benchmark/benchmarks/benchdata + +# With Ninja generator (faster builds) +cmake --preset dev-ninja +cmake --build build/dev-ninja +``` + +### Available Presets + +| Preset | Description | Developer Mode | Build Type | Features | +|--------|-------------|----------------|------------|----------| +| `dev` | Development build | ON | Debug | Tests, warnings as errors, clang-tidy | +| `dev-ninja` | Development with Ninja | ON | Debug | Same as dev, faster builds | +| `release` | Production build | OFF | Release | Library only, optimized | +| `release-ninja` | Release with Ninja | OFF | Release | Same as release, faster | +| `test` | Testing build | ON | Debug | Tests with quality checks | +| `test-ninja` | Testing with Ninja | ON | Debug | Same as test, faster | +| `benchmark` | Benchmark build | OFF | Release | Benchmarks, optimized | +| `benchmark-ninja` | Benchmark with Ninja | OFF | Release | Same as benchmark, faster | +| `sanitize-address` | Address Sanitizer | ON | Debug | ASan for memory errors | +| `sanitize-undefined` | UB Sanitizer | ON | Debug | UBSan for undefined behavior | +| `sanitize-all` | All Sanitizers | ON | Debug | ASan + UBSan | +| `coverage` | Code Coverage | ON | Debug | Coverage instrumentation | +| `tools` | CLI Tools | OFF | Release | Build adaparse tool | +| `single-header` | Single Header | OFF | Release | Amalgamated build | +| `ci` | CI Build | ON | RelWithDebInfo | All checks for CI | + +## Developer Mode + +Developer Mode is a new feature that automatically enables multiple quality checks: + +- **Compiler warnings as errors** - Catch issues early +- **Development assertions** - Internal validation +- **clang-tidy** - Static analysis (if available) +- **cppcheck** - Additional static analysis (if available) + +Enable Developer Mode: + +```bash +# Via preset (recommended) +cmake --preset dev + +# Via command line +cmake -B build -DADA_DEVELOPER_MODE=ON + +# Disable specific checks while keeping Developer Mode +cmake -B build -DADA_DEVELOPER_MODE=ON -DADA_ENABLE_CLANG_TIDY=OFF +``` + +## New CMake Modules + +### cmake/ProjectOptions.cmake + +Centralized configuration for all build options. Defines: + +- All `ADA_*` options +- Developer Mode behavior +- Build type defaults +- Validation and warnings +- Feature summary display + +### cmake/CompilerWarnings.cmake + +Systematic compiler warning management with functions: + +**`ada_set_project_warnings(target)`** +- Applies comprehensive warnings to a target +- Supports MSVC, GCC, Clang, and AppleClang +- Includes platform-specific optimizations (e.g., GCC AVX workaround) +- Can treat warnings as errors + +**`ada_set_sanitizer_flags(target)`** +- Applies sanitizer flags (ASan, UBSan, bounds checking) +- Configures both compile and link flags +- Sets appropriate environment variables + +**`ada_set_standard_settings(target)`** +- Applies standard C++20 requirement +- Sets development checks, logging, testing flags +- Configures feature macros + +### cmake/StaticAnalyzers.cmake + +Integration with static analysis tools: + +**`ada_enable_clang_tidy(target)`** +- Enables clang-tidy for the target +- Uses `.clang-tidy` configuration +- Can treat warnings as errors + +**`ada_enable_cppcheck(target)`** +- Enables cppcheck for the target +- Configures appropriate flags for C++20 +- Suppresses noisy warnings + +**`ada_enable_static_analyzers(target)`** +- Convenience function to enable all analyzers +- Only enables if tools are found + +## Build Options Reference + +### Build Features + +| Option | Default | Description | +|--------|---------|-------------| +| `ADA_TESTING` | OFF | Enable building tests | +| `ADA_BENCHMARKS` | OFF | Enable building benchmarks | +| `ADA_TOOLS` | OFF | Enable building CLI tools | +| `ADA_BUILD_SINGLE_HEADER_LIB` | OFF | Build from single-header files | + +### Library Features + +| Option | Default | Description | +|--------|---------|-------------| +| `ADA_USE_SIMDUTF` | OFF | Enable SIMD Unicode processing | +| `ADA_INCLUDE_URL_PATTERN` | ON | Include URL pattern implementation | +| `ADA_USE_UNSAFE_STD_REGEX_PROVIDER` | OFF | Use std::regex (security-sensitive) | + +### Quality & Analysis + +| Option | Default | Description | +|--------|---------|-------------| +| `ADA_DEVELOPER_MODE` | OFF | Enable developer mode (auto-enables below) | +| `ADA_WARNINGS_AS_ERRORS` | OFF | Treat compiler warnings as errors | +| `ADA_DEVELOPMENT_CHECKS` | OFF | Enable internal assertions | +| `ADA_LOGGING` | OFF | Enable verbose logging | +| `ADA_ENABLE_CLANG_TIDY` | OFF | Enable clang-tidy analysis | +| `ADA_ENABLE_CPPCHECK` | OFF | Enable cppcheck analysis | + +### Sanitizers & Testing + +| Option | Default | Description | +|--------|---------|-------------| +| `ADA_SANITIZE` | OFF | Enable Address Sanitizer | +| `ADA_SANITIZE_UNDEFINED` | OFF | Enable UB Sanitizer | +| `ADA_SANITIZE_BOUNDS_STRICT` | OFF | Strict bounds (GCC only) | +| `ADA_COVERAGE` | OFF | Enable code coverage | + +## Migration Guide + +### Old Approach + +```bash +# Old way (still works) +cmake -B build -DADA_TESTING=ON +cmake --build build +ctest --output-on-failure --test-dir build +``` + +### New Approach (Recommended) + +```bash +# New way with presets +cmake --preset test +cmake --build build/test +ctest --test-dir build/test --output-on-failure + +# Or even simpler with test preset +cmake --preset test +cmake --build --preset test +ctest --preset test +``` + +### For Benchmarks + +```bash +# Old way +cmake -B build -DADA_BENCHMARKS=ON -DADA_USE_UNSAFE_STD_REGEX_PROVIDER=ON -DCMAKE_BUILD_TYPE=Release +cmake --build build +./build/benchmarks/benchdata + +# New way +cmake --preset benchmark +cmake --build --preset benchmark +./build/benchmark/benchmarks/benchdata +``` + +## IDE Integration + +Modern IDEs (Visual Studio Code, CLion, Visual Studio 2019+) automatically detect `CMakePresets.json` and provide: + +- **Preset selection** in the UI +- **IntelliSense/code completion** with correct flags +- **One-click configuration and building** +- **Integrated testing** with CTest + +### VS Code + +1. Install the CMake Tools extension +2. Open the command palette (Ctrl+Shift+P) +3. Select "CMake: Select Configure Preset" +4. Choose a preset (e.g., "dev") +5. Build with "CMake: Build" or F7 + +### CLion + +CLion 2020.3+ automatically loads presets from `CMakePresets.json` in the CMake settings. + +## Best Practices Summary + +1. **Use Presets** - They encode the correct build configurations +2. **Enable Developer Mode** - During development for quality checks +3. **Build Type Matters** - Always use Release for benchmarks +4. **Test with Sanitizers** - Regularly run tests with ASan/UBSan +5. **Static Analysis** - Run clang-tidy in CI or before commits +6. **Separate Builds** - Use different build directories for different configurations + +## Customizing for Your Project + +If you're using Ada as a template or want to customize: + +1. **Add new presets** in `CMakePresets.json` +2. **Modify warnings** in `cmake/CompilerWarnings.cmake` +3. **Configure analyzers** by editing `.clang-tidy` +4. **Add options** in `cmake/ProjectOptions.cmake` + +## Troubleshooting + +### Preset not found + +**Error:** `CMake Error: Could not read presets from ...` + +**Solution:** Requires CMake 3.19+ for presets. Update CMake or use traditional approach. + +### clang-tidy too slow + +**Solution:** Disable in dev preset or use a separate preset: + +```bash +cmake --preset dev -DADA_ENABLE_CLANG_TIDY=OFF +``` + +### Warnings as errors failing + +**Solution:** This is intentional in Developer Mode. Fix the warnings or temporarily disable: + +```bash +cmake --preset dev -DADA_WARNINGS_AS_ERRORS=OFF +``` + +## Performance Notes + +### Build Speed + +Using Ninja generator can significantly speed up builds: + +```bash +# Make (default) +cmake --preset dev +cmake --build build/dev +# Time: ~X seconds + +# Ninja (faster) +cmake --preset dev-ninja +cmake --build build/dev-ninja +# Time: ~X/2 seconds +``` + +Install Ninja: `sudo apt install ninja-build` (Ubuntu/Debian) + +### Development Checks Impact + +Development checks add runtime overhead: + +- **Development/Testing**: ~5-10% slowdown (acceptable) +- **Benchmarking**: ~20-50% slowdown (NOT acceptable) + +**Always benchmark with Release build and checks disabled:** + +```bash +cmake --preset benchmark # Checks auto-disabled +``` + +## CI/CD Integration + +The Ada project's GitHub Actions workflows have been updated to use CMake presets: + +### Updated Workflows + +**ubuntu.yml** - Main Ubuntu CI +```yaml +- name: Prepare (CMake with ci preset) + run: cmake --preset ci -DBUILD_SHARED_LIBS=${{matrix.shared}} -DADA_USE_SIMDUTF=${{matrix.simdutf}} -DADA_BENCHMARKS=ON +``` + +**ubuntu-sanitized.yml** - Address Sanitizer +```yaml +- name: Prepare (CMake with sanitize-address preset) + run: cmake --preset sanitize-address -DBUILD_SHARED_LIBS=${{matrix.shared}} +``` + +**ubuntu-undef.yml** - Undefined Behavior Sanitizer +```yaml +- name: Prepare (CMake with sanitize-undefined preset) + run: cmake --preset sanitize-undefined -DBUILD_SHARED_LIBS=${{matrix.shared}} +``` + +**ubuntu-release.yml** - Release Builds +```yaml +- name: Prepare (CMake with release-ninja preset) + run: cmake --preset release-ninja -DBUILD_TESTING=OFF +``` + +### Benefits in CI + +- **Consistency**: Same preset configurations locally and in CI +- **Maintainability**: Less duplication of CMake flags +- **Clarity**: Preset names document intent (ci, sanitize-address, etc.) +- **Flexibility**: Can still override options via `-D` flags + +## Further Reading + +- [Modern CMake](https://cliutils.gitlab.io/modern-cmake/) +- [CMake Presets Documentation](https://cmake.org/cmake/help/latest/manual/cmake-presets.7.html) +- [cpp-best-practices/cmake_template](https://github.com/cpp-best-practices/cmake_template) +- [Effective Modern CMake](https://gist.github.com/mbinna/c61dbb39bca0e4fb7d1f73b0d66a4fd1) diff --git a/include/ada/common_defs.h b/include/ada/common_defs.h index ac6e660eb..32a459d71 100644 --- a/include/ada/common_defs.h +++ b/include/ada/common_defs.h @@ -40,10 +40,10 @@ #endif // Align to N-byte boundary -#define ADA_ROUNDUP_N(a, n) (((a) + ((n)-1)) & ~((n)-1)) -#define ADA_ROUNDDOWN_N(a, n) ((a) & ~((n)-1)) +#define ADA_ROUNDUP_N(a, n) (((a) + ((n) - 1)) & ~((n) - 1)) +#define ADA_ROUNDDOWN_N(a, n) ((a) & ~((n) - 1)) -#define ADA_ISALIGNED_N(ptr, n) (((uintptr_t)(ptr) & ((n)-1)) == 0) +#define ADA_ISALIGNED_N(ptr, n) (((uintptr_t)(ptr) & ((n) - 1)) == 0) #if defined(ADA_REGULAR_VISUAL_STUDIO) @@ -201,16 +201,17 @@ namespace ada { std::cerr << "FAIL: " << (MESSAGE) << std::endl; \ abort(); \ } while (0); -#define ADA_ASSERT_EQUAL(LHS, RHS, MESSAGE) \ - do { \ - if (LHS != RHS) { \ - std::cerr << "Mismatch: '" << LHS << "' - '" << RHS << "'" << std::endl; \ - ADA_FAIL(MESSAGE); \ - } \ +#define ADA_ASSERT_EQUAL(LHS, RHS, MESSAGE) \ + do { \ + if ((LHS) != (RHS)) { \ + std::cerr << "Mismatch: '" << (LHS) << "' - '" << (RHS) << "'" \ + << std::endl; \ + ADA_FAIL(MESSAGE); \ + } \ } while (0); #define ADA_ASSERT_TRUE(COND) \ do { \ - if (!(COND)) { \ + if (!((COND))) { \ std::cerr << "Assert at line " << __LINE__ << " of file " << __FILE__ \ << std::endl; \ ADA_FAIL(ADA_STR(COND)); \ @@ -223,11 +224,11 @@ namespace ada { #endif #ifdef ADA_VISUAL_STUDIO -#define ADA_ASSUME(COND) __assume(COND) +#define ADA_ASSUME(COND) __assume((COND)) #else #define ADA_ASSUME(COND) \ do { \ - if (!(COND)) { \ + if (!((COND))) { \ __builtin_unreachable(); \ } \ } while (0) diff --git a/include/ada/expected.h b/include/ada/expected.h index 5233a042a..9e332c6b2 100644 --- a/include/ada/expected.h +++ b/include/ada/expected.h @@ -1359,25 +1359,29 @@ class expected : private detail::expected_move_assign_base, #else template - TL_EXPECTED_11_CONSTEXPR auto and_then(F &&f) & -> decltype(and_then_impl( - std::declval(), std::forward(f))) { + TL_EXPECTED_11_CONSTEXPR auto and_then( + F &&f) & -> decltype(and_then_impl(std::declval(), + std::forward(f))) { return and_then_impl(*this, std::forward(f)); } template - TL_EXPECTED_11_CONSTEXPR auto and_then(F &&f) && -> decltype(and_then_impl( - std::declval(), std::forward(f))) { + TL_EXPECTED_11_CONSTEXPR auto and_then( + F &&f) && -> decltype(and_then_impl(std::declval(), + std::forward(f))) { return and_then_impl(std::move(*this), std::forward(f)); } template - constexpr auto and_then(F &&f) const & -> decltype(and_then_impl( - std::declval(), std::forward(f))) { + constexpr auto and_then( + F &&f) const & -> decltype(and_then_impl(std::declval(), + std::forward(f))) { return and_then_impl(*this, std::forward(f)); } #ifndef TL_EXPECTED_NO_CONSTRR template - constexpr auto and_then(F &&f) const && -> decltype(and_then_impl( - std::declval(), std::forward(f))) { + constexpr auto and_then(F &&f) + const && -> decltype(and_then_impl(std::declval(), + std::forward(f))) { return and_then_impl(std::move(*this), std::forward(f)); } #endif @@ -2226,8 +2230,8 @@ template ())), detail::enable_if_t::value> * = nullptr> -constexpr auto expected_map_impl(Exp &&exp, F &&f) - -> ret_t> { +constexpr auto expected_map_impl(Exp &&exp, + F &&f) -> ret_t> { using result = ret_t>; return exp.has_value() ? result(detail::invoke(std::forward(f), @@ -2255,8 +2259,8 @@ template ())), detail::enable_if_t::value> * = nullptr> -constexpr auto expected_map_impl(Exp &&exp, F &&f) - -> ret_t> { +constexpr auto expected_map_impl(Exp &&exp, + F &&f) -> ret_t> { using result = ret_t>; return exp.has_value() ? result(detail::invoke(std::forward(f))) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 9587ee089..1eb7ed111 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,72 +1,80 @@ +# +# Ada Library Target Configuration +# +# This file configures the main Ada library target with: +# - Compiler warnings (via CompilerWarnings.cmake) +# - Static analyzers (via StaticAnalyzers.cmake) +# - Sanitizers and development checks +# - Platform-specific optimizations +# +# ============================================================================ +# Include Modern CMake Modules +# ============================================================================ +include(${PROJECT_SOURCE_DIR}/cmake/CompilerWarnings.cmake) +include(${PROJECT_SOURCE_DIR}/cmake/StaticAnalyzers.cmake) + +# ============================================================================ +# Build Information +# ============================================================================ message(STATUS "Compiler ID : " ${CMAKE_CXX_COMPILER_ID}) message(STATUS "CMAKE_BUILD_TYPE : " ${CMAKE_BUILD_TYPE}) +# ============================================================================ +# Interface Libraries (for embedding) +# ============================================================================ add_library(ada-include-source INTERFACE) target_include_directories(ada-include-source INTERFACE $) + add_library(ada-source INTERFACE) target_sources(ada-source INTERFACE $/ada.cpp) target_link_libraries(ada-source INTERFACE ada-include-source) + +# ============================================================================ +# Main Ada Library Target +# ============================================================================ add_library(ada ada.cpp) + +# C++20 standard requirement target_compile_features(ada PUBLIC cxx_std_20) -target_include_directories(ada PRIVATE $ ) + +# Include directories +target_include_directories(ada PRIVATE $) target_include_directories(ada PUBLIC "$") + +# Position Independent Code (for shared library compatibility) if(NOT DEFINED CMAKE_POSITION_INDEPENDENT_CODE) - # We default to ON for all targets, so that we can use the library in shared libraries. set_target_properties(ada PROPERTIES POSITION_INDEPENDENT_CODE ON) -endif(NOT DEFINED CMAKE_POSITION_INDEPENDENT_CODE) -if(MSVC) - if("${MSVC_TOOLSET_VERSION}" STREQUAL "140") - target_compile_options(ada INTERFACE /W0 /sdl) - set(ADA_LEGACY_VISUAL_STUDIO TRUE) - else() - target_compile_options(ada PRIVATE /WX /W3 /sdl /w34714) # https://docs.microsoft.com/en-us/cpp/error-messages/compiler-warnings/compiler-warning-level-4-c4714?view=vs-2019 - endif() -else(MSVC) - message(STATUS "Assuming GCC-like compiler.") - target_compile_options(ada PRIVATE -Wall -Wextra -Weffc++) - if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") - target_compile_options(ada PRIVATE -Wsuggest-override) - endif() - if((CMAKE_CXX_COMPILER_ID STREQUAL "Clang") OR (CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang")) - target_compile_options(ada PRIVATE -Winconsistent-missing-override) - endif() - target_compile_options(ada PRIVATE -Wfatal-errors -Wsign-compare -Wshadow -Wwrite-strings -Wpointer-arith -Winit-self -Wconversion -Wno-sign-conversion) -endif(MSVC) - -# workaround for GNU GCC poor AVX load/store code generation -if ((CMAKE_CXX_COMPILER_ID STREQUAL "GNU") AND (CMAKE_SYSTEM_PROCESSOR MATCHES "^(i.86|x86(_64)?)$")) - target_compile_options(ada PRIVATE -mno-avx256-split-unaligned-load -mno-avx256-split-unaligned-store) -endif() -if(ADA_DEVELOPMENT_CHECKS) - target_compile_definitions(ada PUBLIC ADA_DEVELOPMENT_CHECKS=1) endif() +# ============================================================================ +# Compiler Warnings +# ============================================================================ +# Apply systematic compiler warnings using modern CMake patterns +ada_set_project_warnings(ada) -if(ADA_SANITIZE) - target_compile_options(ada PUBLIC -fsanitize=address -fno-omit-frame-pointer -fno-sanitize-recover=all) - target_compile_definitions(ada PUBLIC ASAN_OPTIONS=detect_leaks=1) - target_link_libraries(ada PUBLIC -fsanitize=address -fno-omit-frame-pointer -fno-sanitize-recover=all) -endif() -if(ADA_SANITIZE_WITHOUT_LEAKS) - target_compile_options(ada PUBLIC -fsanitize=address -fno-omit-frame-pointer -fno-sanitize-recover=all) - target_link_libraries(ada PUBLIC -fsanitize=address -fno-omit-frame-pointer -fno-sanitize-recover=all) -endif() - -if(ADA_LOGGING) - target_compile_definitions(ada PRIVATE ADA_LOGGING=1) -endif() +# ============================================================================ +# Static Analysis +# ============================================================================ +# Enable clang-tidy and cppcheck if requested +ada_enable_static_analyzers(ada) +# ============================================================================ +# Standard Settings & Compile Definitions +# ============================================================================ +# Apply standard Ada settings (development checks, logging, testing flags, etc.) +ada_set_standard_settings(ada) -if(ADA_TESTING) - target_compile_definitions(ada PRIVATE ADA_TESTING=1) -endif() - -if(ADA_INCLUDE_URL_PATTERN) - target_compile_definitions(ada PRIVATE ADA_INCLUDE_URL_PATTERN=1) -else() - target_compile_definitions(ada PRIVATE ADA_INCLUDE_URL_PATTERN=0) -endif() +# ============================================================================ +# Sanitizers +# ============================================================================ +# Apply sanitizer flags if enabled +ada_set_sanitizer_flags(ada) -if (ADA_USE_SIMDUTF) +# ============================================================================ +# Optional Dependencies +# ============================================================================ +# SIMDUTF for accelerated Unicode processing +if(ADA_USE_SIMDUTF) target_link_libraries(ada PRIVATE simdutf) target_compile_definitions(ada PRIVATE ADA_USE_SIMDUTF) + message(STATUS "Ada library: SIMDUTF support enabled") endif() diff --git a/src/ada_idna.cpp b/src/ada_idna.cpp index 444a86421..800967a95 100644 --- a/src/ada_idna.cpp +++ b/src/ada_idna.cpp @@ -9660,8 +9660,8 @@ std::string to_unicode(std::string_view input) { auto utf8_size = simdutf::utf8_length_from_utf32(tmp_buffer.data(), tmp_buffer.size()); std::string final_utf8(utf8_size, '\0'); - simdutf::convert_utf32_to_utf8(tmp_buffer.data(), tmp_buffer.size(), - final_utf8.data()); + [[maybe_unused]] auto converted_size = simdutf::convert_utf32_to_utf8( + tmp_buffer.data(), tmp_buffer.size(), final_utf8.data()); #else auto utf8_size = ada::idna::utf8_length_from_utf32(tmp_buffer.data(), tmp_buffer.size());