Skip to content

Commit 46b7c5b

Browse files
Generate Single Header via GitHub Workflows (#299)
* Single header generation test * Test pushing csv.hpp to single header * Update cmake-multi-platform.yml * Update documentation * Make single include test optional * Added small redirect * Update CMakeLists.txt * Stabilize Windows CI build * Update cmake-multi-platform.yml
1 parent 172ad8d commit 46b7c5b

14 files changed

Lines changed: 212 additions & 17426 deletions

File tree

.github/workflows/cmake-multi-platform.yml

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ name: CMake on multiple platforms
44

55
on:
66
push:
7-
branches: [ "master", "feb-15-2026" ]
7+
branches: [ "master" ]
88
pull_request:
99
branches: [ "master" ]
1010

@@ -51,6 +51,11 @@ jobs:
5151
with:
5252
submodules: recursive
5353

54+
- name: Set up Python
55+
uses: actions/setup-python@v5
56+
with:
57+
python-version: '3.x'
58+
5459
- name: Set reusable strings
5560
# Turn repeated input strings (such as the build output directory) into step outputs. These step outputs can be used throughout the workflow file.
5661
id: strings
@@ -64,17 +69,27 @@ jobs:
6469
run: >
6570
cmake -B ${{ steps.strings.outputs.build-output-dir }}
6671
-DCSV_CXX_STANDARD=${{ matrix.cxx_standard }}
72+
-DCSV_BUILD_SINGLE_INCLUDE_TEST=ON
6773
-DCMAKE_CXX_COMPILER=${{ matrix.cpp_compiler }}
6874
-DCMAKE_C_COMPILER=${{ matrix.c_compiler }}
6975
-DCMAKE_BUILD_TYPE=${{ matrix.build_type }}
7076
-S ${{ github.workspace }}
7177
7278
- name: Build
7379
# Build your program with the given configuration. Note that --config is needed because the default Windows generator is a multi-config generator (Visual Studio generator).
80+
# single_include_test is excluded from the default "all" target and is built explicitly in the next step.
7481
run: cmake --build ${{ steps.strings.outputs.build-output-dir }} --config ${{ matrix.build_type }}
7582

83+
- name: Single-header smoke test
84+
if: matrix.os != 'windows-latest'
85+
# Explicitly build the single-header smoke-test target so CI validates the freshly generated amalgamated header.
86+
# TODO: Re-enable on Windows after fixing single_include_test target generation/project discovery.
87+
run: cmake --build ${{ steps.strings.outputs.build-output-dir }} --config ${{ matrix.build_type }} --target single_include_test
88+
7689
- name: Test
7790
working-directory: ${{ steps.strings.outputs.build-output-dir }}
7891
# Execute tests defined by the CMake configuration. Note that --build-config is needed because the default Windows generator is a multi-config generator (Visual Studio generator).
7992
# See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail
8093
run: ctest --build-config ${{ matrix.build_type }}
94+
95+

.github/workflows/codeql.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ name: CodeQL Analysis
22

33
on:
44
push:
5-
branches: [ "master", "feb-15-2026" ]
5+
branches: [ "master" ]
66
pull_request:
77
branches: [ "master" ]
88
schedule:

.github/workflows/docs.yml

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: Documentation
1+
name: Documentation and Single Header
22

33
on:
44
push:
@@ -33,6 +33,16 @@ jobs:
3333
- name: Run Doxygen
3434
run: doxygen Doxyfile
3535

36+
- name: Set up Python
37+
uses: actions/setup-python@v5
38+
with:
39+
python-version: '3.x'
40+
41+
- name: Generate single header
42+
# Drops csv.hpp into docs/html so it is served at the same Pages URL
43+
# as the Doxygen docs: https://<owner>.github.io/csv-parser/csv.hpp
44+
run: python single_header.py docs/html/csv.hpp
45+
3646
- name: Upload Pages artifact
3747
uses: actions/upload-pages-artifact@v3
3848
with:

.github/workflows/sanitizers.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ name: Memory and Thread Sanitizers
22

33
on:
44
push:
5-
branches: [ "master", "feb-15-2026" ]
5+
branches: [ "master" ]
66
pull_request:
77
branches: [ "master" ]
88

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,12 @@ docs/html
5454
*.gcno
5555
*.gcov
5656

57+
# Generated single-header files
58+
# These are auto-generated by single_header.py and deployed via CI
59+
# Users should download from: https://vincentlaucsb.github.io/csv-parser/csv.hpp
60+
single_include_test/csv.hpp
61+
build/single_include_generated/
62+
5763
# =========================
5864
# Operating System Files
5965
# =========================

CMakeLists.txt

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ else()
88
endif(CSV_CXX_STANDARD)
99

1010
option(BUILD_PYTHON "Build Python Binding" OFF)
11+
option(CSV_BUILD_SINGLE_INCLUDE_TEST "Build single-header smoke test (requires Python)" OFF)
1112
option(ENABLE_CODE_COVERAGE "Enable code coverage instrumentation" OFF)
1213

1314
message("Building CSV library using C++${CMAKE_CXX_STANDARD}")
@@ -92,15 +93,44 @@ if (CSV_DEVELOPER)
9293
find_package(Python3 COMPONENTS Interpreter)
9394
endif()
9495
if(Python3_Interpreter_FOUND OR PYTHONINTERP_FOUND)
95-
add_custom_target(generate_single_header
96-
COMMAND ${Python3_EXECUTABLE} single_header.py > single_include/csv.hpp
97-
COMMAND ${Python3_EXECUTABLE} single_header.py > single_include_test/csv.hpp
96+
if(Python3_Interpreter_FOUND)
97+
set(CSV_PYTHON_EXECUTABLE ${Python3_EXECUTABLE})
98+
else()
99+
set(CSV_PYTHON_EXECUTABLE ${PYTHON_EXECUTABLE})
100+
endif()
101+
102+
set(CSV_SINGLE_INCLUDE_GENERATED_DIR ${CSV_BUILD_DIR}/single_include_generated)
103+
set(CSV_SINGLE_INCLUDE_GENERATED_HEADER ${CSV_SINGLE_INCLUDE_GENERATED_DIR}/csv.hpp)
104+
file(GLOB_RECURSE CSV_SINGLE_HEADER_INPUTS CONFIGURE_DEPENDS
105+
${CSV_INCLUDE_DIR}/*.hpp
106+
${CSV_INCLUDE_DIR}/*.h
107+
${CSV_INCLUDE_DIR}/*.cpp
108+
)
109+
110+
add_custom_command(
111+
OUTPUT ${CSV_SINGLE_INCLUDE_GENERATED_HEADER}
112+
COMMAND ${CMAKE_COMMAND} -E make_directory ${CSV_SINGLE_INCLUDE_GENERATED_DIR}
113+
COMMAND ${CSV_PYTHON_EXECUTABLE} single_header.py ${CSV_SINGLE_INCLUDE_GENERATED_HEADER}
98114
WORKING_DIRECTORY ${CSV_ROOT_DIR}
115+
DEPENDS ${CSV_ROOT_DIR}/single_header.py ${CSV_SINGLE_HEADER_INPUTS}
116+
COMMENT "Generating single-header csv.hpp"
99117
)
100-
# Single header compilation test
101-
add_subdirectory(single_include_test)
118+
119+
add_custom_target(generate_single_header
120+
DEPENDS ${CSV_SINGLE_INCLUDE_GENERATED_HEADER}
121+
)
122+
123+
# Single header compilation test (optional, mainly for validation)
124+
if(CSV_BUILD_SINGLE_INCLUDE_TEST)
125+
# Keep this target out of the default "all" build so CI can run it
126+
# explicitly in its dedicated smoke-test step.
127+
add_subdirectory(single_include_test EXCLUDE_FROM_ALL)
128+
endif()
102129
else()
103130
message(WARNING "Python3 not found, skipping target 'generate_single_header'.")
131+
if(CSV_BUILD_SINGLE_INCLUDE_TEST)
132+
message(FATAL_ERROR "CSV_BUILD_SINGLE_INCLUDE_TEST=ON requires a Python 3 interpreter for single-header generation.")
133+
endif()
104134
endif()
105135

106136
# Documentation

README.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,14 @@ While C++17 is recommended, C++11 is the minimum version required. This library
118118
[Martin Moene's string view library](https://github.com/martinmoene/string-view-lite) if `std::string_view` is not available.
119119
120120
### Single Header
121-
This library is available as a single `.hpp` file under [`single_include/csv.hpp`](single_include/csv.hpp).
121+
**[📥 Download csv.hpp](https://vincentlaucsb.github.io/csv-parser/csv.hpp)** — Available on GitHub Pages
122+
123+
Or copy the URL:
124+
```
125+
https://vincentlaucsb.github.io/csv-parser/csv.hpp
126+
```
127+
128+
The file is automatically generated and deployed on every commit to `master`, ensuring you always have the latest version.
122129
123130
### CMake Instructions
124131
If you're including this in another CMake project, you can simply clone this repo into your project directory,

include/csv.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
CSV for C++, version 2.5.0
2+
CSV for C++, version 2.5.1
33
https://github.com/vincentlaucsb/csv-parser
44
55
MIT License

single_header.py

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
from collections import namedtuple
2+
from contextlib import redirect_stdout
23
import os
34
import re
5+
import sys
46

57
CPP_SEP = '/'
68
Include = namedtuple('Include', ['path', 'line_no'])
@@ -194,7 +196,7 @@ def process_file(path: Path):
194196

195197
return header_concat
196198

197-
if __name__ == "__main__":
199+
def generate_single_header():
198200
''' Iterate over every .cpp and .hpp file '''
199201
headers = []
200202
sources = []
@@ -226,9 +228,19 @@ def process_file(path: Path):
226228

227229
for cpp in sources:
228230
source_collate += file_strip(cpp) + '\n'
229-
231+
230232
# Generate hpp file
231233
print("#pragma once")
232234
print(header_concat.replace(
233235
"#define CSV_INLINE", "#define CSV_INLINE inline").replace(
234-
"/** INSERT_CSV_SOURCES **/", source_collate))
236+
"/** INSERT_CSV_SOURCES **/", source_collate))
237+
238+
239+
if __name__ == "__main__":
240+
if len(sys.argv) > 1:
241+
output_path = sys.argv[1]
242+
with open(output_path, mode='w', newline='') as outfile:
243+
with redirect_stdout(outfile):
244+
generate_single_header()
245+
else:
246+
generate_single_header()

single_include/README.md

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
# Single Header Distribution
2+
3+
> **`single_include/csv.hpp` is now a small compatibility shim, not the full amalgamated header.**
4+
5+
## For Users
6+
7+
**[📥 Download csv.hpp](https://vincentlaucsb.github.io/csv-parser/csv.hpp)** — Available on GitHub Pages
8+
9+
Or copy the URL:
10+
```
11+
https://vincentlaucsb.github.io/csv-parser/csv.hpp
12+
```
13+
14+
This file is automatically generated and deployed on every commit to `master`, ensuring you always have the latest version.
15+
16+
### Usage
17+
18+
Once downloaded, simply include it in your project:
19+
20+
```cpp
21+
#include "csv.hpp"
22+
23+
// Use the library as normal
24+
```
25+
26+
No build configuration needed — everything is self-contained in the single file.
27+
28+
---
29+
30+
## For Maintainers
31+
32+
### Generating the Single Header Locally
33+
34+
To generate the amalgamated header yourself:
35+
36+
```bash
37+
python single_header.py output_path/csv.hpp
38+
```
39+
40+
This reads all source files from `include/` and produces a completely self-contained header file.
41+
42+
### Validation
43+
44+
The CI pipeline automatically validates the generated header by:
45+
1. Generating it in the build tree during CMake configuration
46+
2. Compiling it cleanly with `single_include_test` (multi-TU smoke test)
47+
3. Deploying the result to GitHub Pages
48+
49+
Run the smoke test locally:
50+
51+
```bash
52+
cd build
53+
cmake --build . --target single_include_test
54+
```
55+
56+
### Important Notes
57+
58+
- **Do not manually commit `csv.hpp`** — it is generated and deployed by CI
59+
- **Do not modify generated files** — always edit the source files in `include/`
60+
- **The build-generated header is under `build/single_include_generated/`** — this is the stable artifact used by `single_include_test`
61+
- **GitHub Pages serves the canonical version** — see [Distribution Overview](#for-users) above
62+
63+
### Distribution Model
64+
65+
- **Build-time:** CMake generates into build tree for testing
66+
- **CI/Release:** Deployed to GitHub Pages for public consumption
67+
- **Repository:** Source files only (no pre-generated artifacts)
68+
69+
This model ensures:
70+
- No stale artifacts in git
71+
- Always-fresh version on GitHub Pages
72+
- Deterministic, reproducible generation
73+
- Clean maintenance burden

0 commit comments

Comments
 (0)