# My C++ setup for personal projects


Here's a quick overview of my repo setup for any new C++ projects
that I create. I'm gonna start with an empty repo and bring it up to
an initial stage where all my preferred tooling is available and ready.

## Empty repo scaffolding

Here's the [link to the repo](https://gitlab.com/twdev_projects/cppsetup), if you're not interested in the walk through.

### Build system

Starting with an empty git repo, first thing I like to do is just to
create `main.cpp` with an empty `main` function.  I use [vim-luasnips](https://github.com/L3MON4D3/LuaSnip) so, creating that is instantaneous.


By now, I'm sure you know that I like `meson` and for personal projects it's
perfect! So, you guessed it, I establish the build system:

```console
meson init -l cpp .
```

This will create `meson.build` which is a good starting point.
Sometimes, you need to adjust the filenames used to create the
executable so, I do that. Having the build system, I usually bootstrap
the build directory and link the compilation database:

```console
meson setup bld
ln -sf bld/compile_commands.json .
echo bld >>.gitignore
```

I mostly use `bld` as the build directory so, I add it to `.gitignore`
and commit the link to `compile_commands.json` to the repo for
convenience.  This concludes my first commit:

```console
git add main.cpp .gitignore meson.build compile_commands.json
git commit -m "chore: initial"
```


Having the `compile_commands.json` automatically updated by `meson`
means that LSP within my editor will always work, which is great!

### Pre-commit hooks

Right, since this is a C++ project... I'll continue with [Python poetry](https://python-poetry.org/).  Wait, **what**?

Yes, that's not a mistake. Usually people have their own pre-commit
scripts or install them manually, but I decided that it's not really
worth to be a purist and reinvent the wheel as far as that goes.
Python tooling is good and it save a lot of manual steps. I realise
that this may not be a preferred way for everyone, it does work for me
though so, hear me out :).

Okay, disclaimer aside, let's continue.  Yep, Python poetry:


```console
poetry -q init
```


This will create `pyproject.toml`.  The contents are not really that
important as I mostly gonna use it as a development tools package manager. Having
that, let's install some basic tools. First pre-commit hook:

```console
poetry add --group dev pre-commit
```

For conventional commits support, I'll need `commitizen`:

```console
poetry add --group dev commitizen
```

With that done, it's time to create `pre-commit` config file:

```console
touch `.pre-commit-config.yaml`
```

Here's my preferred set of hooks:

```
# See https://pre-commit.com for more information
# See https://pre-commit.com/hooks.html for more hooks
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
  rev: v4.4.0
  hooks:
  - id: trailing-whitespace
  - id: end-of-file-fixer
  - id: check-added-large-files

- repo: https://github.com/pocc/pre-commit-hooks
  rev: v1.3.5
  hooks:
  - id: clang-format
    stages: [pre-commit]
  - id: clang-tidy
    stages: [pre-commit]

- repo: https://github.com/commitizen-tools/commitizen
  rev: 3.2.1
  hooks:
  - id: commitizen
    stages: [commit-msg]
```

First three (trailing-whitespace,
end-of-line-fixer, check-added-large-files) are pretty standard, just
to prevent polluting the repo with white space noise and rubbish binary
files.

I like to use hooks from
[github/pocc](https://github.com/pocc/pre-commit-hooks) which
integrate clang tools. Since clang requires gcc ang libstdc++, I don't
install it via `poetry` but rather rely on versions provided system
wide, managed by OS package manager:

    # Arch linux
    pacman -S clang

    # macOS
    brew install llvm

Your mileage may vary but hopefully you get the gist.

Additionally, I recently became a fan of [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/) so,
I'm integrating a tool to validate my commits against that as well.

Now it's time to install the hooks:

```console
poetry run pre-commit install --hook-type pre-commit
poetry run pre-commit install --hook-type commit-msg
```

Since, pre-commit is run locally, re-installation of hooks is required
on every freshly cloned repo so, to ease the burden of doing this, I
usually add a small script to the repo:

```console
mkdir scripts
touch scripts/setup_env.sh
chmod +x scripts/setup_env.sh
```

The contents of which are:

```console
#!/bin/bash

# install tools in venv
poetry install

# install pre-commit hooks
poetry run pre-commit install --hook-type pre-commit
poetry run pre-commit install --hook-type commit-msg

# setup build directory
meson setup bld
```

All of that concludes the second commit:


```console
git add .pyproject.toml poetry.lock .pre-commit-config.yaml scripts
git commit -m "chore: setup pre-commit"
```


This already ran all the configured hooks which is great!

```console
$ git commit -m "chore: setup pre-commit"
trim trailing whitespace.................................................Passed
fix end of files.........................................................Passed
check for added large files..............................................Passed
clang-format.........................................(no files to check)Skipped
clang-tidy...........................................(no files to check)Skipped
commitizen check.........................................................Passed
[master 9787be7] chore: setup pre-commit
 4 files changed, 229 insertions(+)
 create mode 100644 .pre-commit-config.yaml
 create mode 100644 poetry.lock
 create mode 100644 pyproject.toml
 create mode 100644 scripts/setup_env.sh
```

### clang-format

Now, I've added `.clang-format` to `pre-commit` hooks but I didn't configure it yet.

    clang-format --style=Google --dump-config >.clang-format

I usually just start with Google style and adjust it as I go.  The default template is ready to be committed:

    git add .clang-format
    git commit -m "build: add clang-format configuration"


### Editors configuration

All of this integrates nicely with editors I use. I mostly use neovim
but occasionally jump into emacs - no specific reasons for that really, just
depends on the mood :). In nvim, I use
[vim-codefmt](https://github.com/google/vim-codefmt) which picks up
`clang-format` configuration automatically and applies formatting
every time when saving files.  In emacs, there's `clang-format` plugin
available as well which does the same thing.

#### Conventional commits

This is something that I really enjoy. The idea is that, the commit
format is kind of "structured". Thanks to that, it's possible to
generate changelogs from git commits very easily. Read through the
[conventional commits](https://www.conventionalcommits.org/en/v1.0.0/)
web page for more details.

I've installed [commitizen](https://github.com/commitizen/cz-cli) as a
`commit-msg` hook so, if the commit message doesn't adhere to the
format, it will be rejected. You can try it out yourself. If you clone
my [test repo](https://gitlab.com/twdev_projects/cppsetup), run the
`scripts/setup_env.sh` - which installs all the discussed tools,
`commitizen` will become part of the `pre-commit` hooks so committing i.e.:

```console
594:heimdall cppsetup 0 (master) $ echo hello >hello
595:heimdall cppsetup 0 (master) $ git add hello
596:heimdall cppsetup 0 (master +) $ git commit -m "This message is not a conventional commit message"
trim trailing whitespace.................................................Passed
fix end of files.........................................................Passed
check for added large files..............................................Passed
clang-format.........................................(no files to check)Skipped
clang-tidy...........................................(no files to check)Skipped
commitizen check.........................................................Failed
- hook id: commitizen
- exit code: 14

commit validation: failed!
please enter a commit message in the commitizen format.
commit "": "This message is not a conventional commit message"
pattern: (?s)(build|ci|docs|feat|fix|perf|refactor|style|test|chore|revert|bump)(\(\S+\))?!?:( [^\n\r]+)((\n\n.*)|(\s*))?$
```

Similarly, if I'm committing from either emacs magit or vim-fugitive, the commit will be rejected.

#### Changelogs

With all of this in place, generation of a changelog is as easy as:

    poetry run cz changelog

Just as an example, the following git history:

```console
commit a0bd53587f318dc0c8d27f5db1c811144e65742f (HEAD -> master, tag: v0.0.2)
Author: Tomasz Wisniewski <tomasz.wisni3wski@gmail.com>
Date:   Mon May 29 20:48:34 2023 +0100

    fix: correct values returned from foo & bar

    Values returned by these functions were incorrect.  This change
    introduces the correct values.

commit 8c571759af25923cdf801f1961148cd31b5bb6f2
Author: Tomasz Wisniewski <tomasz.wisni3wski@gmail.com>
Date:   Mon May 29 20:46:39 2023 +0100

    feat: add feature bar

    This commit introduces feature bar which further extends what `foo` has
    initially introduced.

commit 5dfb7c3e44ca8ec38c2ffdffbbfc2b636e136acc (tag: v0.0.1)
Author: Tomasz Wisniewski <tomasz.wisni3wski@gmail.com>
Date:   Mon May 29 20:44:53 2023 +0100

    feat: add feature foo

    This change adds a super important feature foo that calculates the
    correct return code.
```

Will result in `CHANGELOG.md` looking like so:

```
$ cat CHANGELOG.md
## v0.0.2 (2023-05-29)

### Feat

- add feature bar

### Fix

- correct values returned from foo & bar

## v0.0.1 (2023-05-29)

### Feat

- add feature foo
```

## Why do I like this setup?

I think that using python poetry to manage dependencies has a lot of
advantages. There's less effort required to manage the tooling.
Integrating new tools is easy. It also helps to create reproducible
development environments so, no special setup instructions are
required for new devs working on your project.

This of course changes a lot and I'm not saying that this is best
setup ever period. I'm sure that it's gonna evolve further and
possibly I'm gonna find a better approach to some of the solutions I
currently use.

