11# C++/Python Tool Template
22
3- A reusable template for building a C++ commandline tool with a Python CLI wrapper, complete with GitHub Actions for CI,
4- Docker release, and Conda release. This template lets you jump straight into implementing a C++ tool without setting
5- up all the build, test, container, and packaging boilerplate code from scratch.
3+ A reusable template for building a C++ commandline tool (using HTSlib) with a Python CLI wrapper, complete with GitHub
4+ Actions for CI, Docker release, and Conda release. This template lets you jump straight into implementing a C++ tool
5+ without setting up all the build, test, container, and packaging boilerplate code from scratch.
66
77Template Repo Combines:
8- - A ** C++ executable** (` linecount ` - compiled with CMake)
8+ - A ** C++ executable** (` aligncount_cpp ` - compiled with CMake)
99- A ** Python wrapper** (` aligncount ` - installed via pip)
1010- Built-in ** unit tests** (C++ and Python)
1111- A ** multi-stage Dockerfile** (builds, tests, and packages a wheel)
1212- A ** Conda recipe** (creates a cross-platform packages for Linux/macOS)
13- - GitHub Actions for CI, Docker release, and Conda release
13+ - GitHub Actions for CI, Docker/Conda release, styling, and documentation
1414
1515
16- ## [ TODO] How To
16+ ## [ TODO] How to use this template
1717
1818---
19-
20- ## Repository Layout
21-
22- ├── CMakeLists.txt
23- ├──.github
24- │ └── workflows
25- │ ├── ci.yml
26- │ ├── conda_release.yml
27- │ └── docker_release.yml
28- ├── setup.py
29- ├── setup.cfg
30- ├── pyproject.toml
31- ├── src/
32- │ └── main.cpp ← C++ “linecount” implementation
33- ├── cli/
34- │ ├── __init__.py
35- │ └── entrypoint.py ← Python wrapper (“aligncount” console script)
36- ├── tests/
37- │ ├── cpp/
38- │ │ └── test_main.cpp ← C++ unit test (Doctest + CTest)
39- │ └── python/
40- │ └── test_wrapper.py ← Python unit test for format_output()
41- ├── Dockerfile ← Multi-stage Docker build + test
42- └── .gitignore
43-
44- ---
45-
46- ## C++ Executable (linecount)
47-
48- - Source: src/main.cpp
49- - Build system: CMake (CMakeLists.txt)
50- - Functionality: Counts non-header lines (simulates an “alignment counter”)
51- - Unit test: tests/cpp/test_main.cpp using Doctest; run via CTest
52-
53- To build manually while at repo root:
54-
55- mkdir build
56- cd build
57- cmake .. -DCMAKE_BUILD_TYPE=Release
58- cmake --build . --parallel $(nproc)
59- # Now ./linecount is available in build/
60- ./linecount sample.sam
61-
62- ---
63-
64- ## Python Wrapper (aligncount)
65-
19+ ## Tool Descriptions
20+ ### Python Wrapper (aligncount)
6621- Source: cli/entrypoint.py
6722- Entry point: aligncount console script (configured in setup.py)
68- - Logic:
69- 1 . Parses subcommands (count-mapped or count-unmapped)
70- 2 . Checks that the input SAM file exists and is non-empty
71- 3 . Calls ` linecount <path> ` and formats output via format_output(cmd, raw_bytes)
72-
23+ - Functionality:
24+ - Parses subcommands (count-mapped or count-unmapped)
25+ - Checks that the input SAM file exists and is non-empty
26+ - Calls ` aligncount_cpp -a <path> ` and writes to count stdout
27+ ### C++ Executable (aligncount_cpp)
28+ - Source: cpp/standalone/source/main.cpp
29+ - Build system: CMake (CMakeLists.txt)
30+ - Functionality: Counts the number of SAM records
31+ ---
32+ ## Installation
33+ ### Build and install the Python Wrapper and the C++ tool
7334To install in a virtualenv while at repo root:
7435
7536 python3 -m venv venv
7637 source venv/bin/activate
77- pip install --upgrade pip setuptools scikit-build pytest
38+ pip install --upgrade pip setuptools scikit-build
7839 pip install .
79- # Now you have:
80- # - linecount (C++ binary, in venv/bin)
81- # - aligncount (Python wrapper, in venv/bin)
82-
83- Run the wrapper (just counts file lines that don't start with ` @ ` ):
8440
85- aligncount count-mapped -i sample.sam
86- aligncount count-unmapped -i sample.sam
41+ Now you have:
42+ - aligncount_cpp (C++ binary, in venv/bin)
43+ - aligncount (Python wrapper, in venv/bin)
8744
88- ---
89-
90- ## Unit Tests
45+ Run the wrapper (counts the number of SAM records in a file):
9146
92- ### C++ Tests
93-
94- - Framework: Doctest (fetched via ExternalProject_Add in CMake)
95- - Test file: tests/cpp/test_main.cpp
96- - Run:
97-
98- cd build
99- ctest --output-on-failure
47+ aligncount count-mapped -a sample.sam
48+ aligncount count-unmapped -a sample.sam
10049
101- ### Python Tests
102-
103- - Framework: pytest
104- - Test file: tests/python/test_wrapper.py
105- - Run:
106-
107- source venv/bin/activate
108- pytest -q
109-
110- ---
111-
112- ## Dockerfile
50+ Note, the installation command implicitly builds the C++ tool via scikit-build/cmake. If you want to
51+ install the Python Wrapper by itself do (does not install aligncount_cpp):
52+
53+ python3 -m venv venv
54+ pip install --upgrade pip setuptools
55+ cd cli
56+ pip install .
11357
58+ ### Dockerfile
11459A multi-stage Dockerfile builds and tests everything, then produces a minimal runtime image:
115-
116- 1 . Builder stage (ubuntu:22.04):
60+ 1 . Builder stage:
11761 - Installs build-time dependencies (CMake, Git, Python dev headers, pip)
118- - Compiles C++ (linecount) and runs CTest
119- - Builds a Python wheel (bundles linecount + aligncount)
120-
121- 2 . Final stage (ubuntu:22.04):
62+ - Compiles C++ (aligncount_cpp) and runs CTest
63+ - Builds a Python wheel (bundles aligncount_cpp + aligncount)
64+ 2 . Final stage:
12265 - Installs runtime dependencies (Python, pip)
123- - Installs the wheel (placing linecount and aligncount into /usr/local/bin)
124- - Runs the Python unit test (pytest test_wrapper.py)
66+ - Installs the wheel (placing aligncount_cpp and aligncount into /usr/local/bin)
67+ - Runs the Python wrapper unit tests
12568 - Sets ENTRYPOINT [ "aligncount"]
12669
12770Build and test in one command:
12871
129- docker build -t your-org/ aligncount-demo :latest .
72+ docker build -t aligncount:latest .
13073
13174Run:
13275
133- docker run --rm your-org/ aligncount-demo :latest --help
76+ docker run --rm aligncount:latest --help
13477
135- ---
13678
137- ## Conda-Build Recipe
79+ ### Manually build the C++ tool aligncount_cpp
80+ To build manually while at repo root:
13881
139- The ` conda-recipe/meta.yaml ` contains instructions for the conda-build command to create the conda package for different
140- platforms, skipping windows. The recipe expects certain environment variables to be set, which are set by the GitHub
141- action ` conda_release.yml ` . The expected environment variables are:
82+ mkdir build
83+ cd build
84+ cmake ../cpp/standalone -DCMAKE_BUILD_TYPE=Release
85+ cmake --build .
14286
143- ```
144- REPO_NAME # Name of the repo, e.g. cpp-python-tool-template
145- VERSION # Tool version, e.g. 0.0.1
146- TAR_URL # URL to the repo archive, e.g. https://github.com/user/foo/archive/refs/tags/v0.0.1.tar.gz
147- SHA256 # SHA256 checksum of the repo archive
148- REPO_HOME # URL to the repo home, e.g. https://github.com/user/foo
149- ```
87+ Now you have aligncount_cpp available in the build folder. Run the tool to counts SAM records:
88+
89+ ./aligncount -a sample.sam
15090
15191---
92+ ## Unit Tests
15293
153- ## GitHub Actions
94+ ### C++ Tests
95+ To run the C++ tool tests at repo root do:
96+
97+ cmake -S cpp/test -B build/test
98+ cmake --build build/test
99+ CTEST_OUTPUT_ON_FAILURE=1 cmake --build build/test --target test
100+
101+ ### Python Tests
102+ To run the python wrapper tests at repo root do:
154103
155- ### .github/workflows/ci.yml
104+ export PYTHONPATH="$PWD/cli:${PYTHONPATH}"
105+ pytest tests
156106
157- This workflow is triggered whenever something is commited to the main branch. It builds the tool and runs the unit tests .
107+ The export command is required if the wrapper is not installed .
158108
109+ ---
110+ ## GitHub Action Workflows
111+
112+ ### On new tag releases:
113+ - ` .github/workflows/conda_release.yml ` Creates and publishes a conda package for various platforms.
114+ - ` .github/workflows/release_release.yml ` Compiles and publishes the Dockerfile to docker hub.
115+ - ` .github/workflows/cpp_documentation.yml ` Builds and publishes documentation for the C++ tool.
116+ ### On commits or pull requests to main/master branch:
117+ - ` .github/workflows/cpp_wrapper_ci.yml ` Builds, installs, and tests the C++ tool and wrapper on ubuntu.
118+ - ` .github/workflows/cpp_install.yml ` Builds, installs, and tests the C++ tool on ubuntu.
119+ - ` .github/workflows/cpp_macos.yml ` Builds and tests the C++ tool on MacOS.
120+ - ` .github/workflows/cpp_standalone.yml ` Builds the C++ tool on ubuntu.
121+ - ` .github/workflows/cpp_ubuntu.yml ` Tests the C++ tool on ubuntu.
122+ - ` .github/workflows/cpp_style.yml ` Checks C++ and CMake source style.
123+ ---
159124### .github/workflows/docker_release.yml
160125
161126This workflow is triggered whenever a new tag for the repo is released. It builds the docker image for linux/amd64
@@ -174,20 +139,26 @@ e.g. cpp-python-tool-template.
174139The workflow assumes your anaconda username and token are stored as repository secrets under ` ANACONDA_USER `
175140and ` ANACONDA_TOKEN ` .
176141
177- ## .gitignore
142+ ---
143+ ## Conda-Build Recipe
178144
179- Included patterns to ignore:
180- - macOS system files (.DS_Store, ._ * )
181- - CMake and CLion build directories (build/, cmake-build-debug/, _ skbuild/, wheelhouse/)
182- - Python venvs and caches (venv/, __ pycache__ /, .pytest_cache/, * .egg-info/)
183- - IDE files (PyCharm/CLion .idea/, * .iml)
184- - Conda build artifacts (conda-bld/, pkgs/)
145+ The ` conda-recipe/meta.yaml ` contains instructions for the conda-build command to create the conda package that
146+ contains the C++ tool and the python wrapper. The recipe expects certain environment variables to be set, which
147+ are set by the GitHub action ` conda_release.yml ` . The expected environment variables are:
185148
149+ ```
150+ REPO_NAME # Name of the repo, e.g. cpp-python-tool-template
151+ VERSION # Tool version, e.g. 0.0.1
152+ TAR_URL # URL to the repo archive, e.g. https://github.com/user/foo/archive/refs/tags/v0.0.1.tar.gz
153+ SHA256 # SHA256 checksum of the repo archive
154+ REPO_HOME # URL to the repo home, e.g. https://github.com/user/foo
155+ ```
186156---
157+ ## Miscellaneous Notes
187158
159+ ---
188160## What’s Next
189161
190- - Customize: Adapt this template to use the https://github.com/filipdutescu/modern-cpp-template , and include htslib
191- - Write a how-to section detailing how to clone the repo and make any necessary edits
162+ - See github issues for this repo
192163
193164With this template, you have a fully tested building, packaging, and distribution pipeline out of the box. Happy coding!
0 commit comments