This document is a guide for developers who want to contribute to the megafauna library. It explains the organization of the software project and sets guidelines for code style, structure, and format. This document is only about syntax only.
After contributing something, don’t forget to add your name to:
- the file header in a new line starting with
SPDX-FileCopyrightText: ...
(following the REUSE standard), - the “Authors” section in the
README.md
, and - the list of authors in the citation files CITATION.cff and codemeta.json.
Note that the authors list in the Zenodo archive is automatically derived from the contributors in the Git history.
- The
master
branch is reserved for stable releases, tagged with the version numbers. - This repository follows Vincent Driessen’s Successful Git Branching Model.
- If you are new to Git branching, check out this tutorial: Learn Git Branching
This project follows Semantic Versioning:
- The pattern of realease tas is
MAJOR.MINOR.PATCH
(e.g.,1.9.1
or1.12.0
). - If the new version cannot read old instruction files anymore or breaks the library interface, increment
MAJOR
. - If the new version introduces a new feature, but still interoperates like the old version, increment
MINOR
. - If the new version only fixes bugs, extends or amends the documentation or refactors code, increment
PATCH
.
Prerelease versions stay on the develop
branch.
You can create tags, but don’t merge into the master
branch.
The tag name should have some meaningful and numbered appendix to the release version it is moving towards.
For example, if the prerelease prepares version 1.3.0
, the tag may be 1.3.0-alpha.1
in the early development phase.
For final testing you may use 1.3.0-beta.1
, 1.3.0-beta.2
, and so on.
Don’t write the prerelease version into CMakeLists.txt
, codemeta.json
, and CITATION.cff
.
That would be difficult to maintain because if you don’t revert the version number in these files to 0.0.0
(which indicates development) immediately and just continue to commit, you end up with different commits containing the same version number in the metadata.
The version numbers in CMakeLists.txt
, codemeta.json
, and CITATION.cff
are really only for actual releases, which are citeable and documented on Read the Docs.
Each merge into the master
branch is a release and should have a Git tag.
- Switch to the master branch:
git switch master
(orgit checkout master
) - Start the merge, but don’t let Git commit yet:
git merge develop --no-ff --no-commit
. Now we have time to add a few things to the merge commit. - Make sure all your changes are listed in
CHANGELOG.md
, following the formatting guidelines there.- Rename the “Unreleased” section to the to-be-released version in
CHANGELOG.md
. - At the bottom of the file, add the URL for the release, comparing it to the previous version. Orient yourself by the existing link URLs.
- Rename the “Unreleased” section to the to-be-released version in
- Set the new version in
CMakeLists.txt
underVERSION
. - Update metadata files:
- Set the version and the
date-released:
field inCITATION.cff
. The date format isYYYY-MM-DD
. - Set the
"version":
and"dateModified":
fields incodemeta.json
.
- Set the version and the
- After staging the modified files, create the merge commit:
git commit -m 'Release version X.X.X'
- Push and create a new release on GitHub, which will trigger Zenodo to archive the code and mint a DOI.
- The release and the tag description should summarize the changes (which you can copy-paste from
CHANGELOG.md
. - The name of the tag and the release is just the exact version, e.g.
0.1.2
.
- The release and the tag description should summarize the changes (which you can copy-paste from
- Fast-forward the
develop
branch:git switch develop && git merge --ff master
- Your first commit in
develop
resets everything so that it cannot be confused with a released version:- Set
VERSION 0.0.0
inCMakeLists.txt
. - Set
version: 0.0.0
inCITATION.cff
, and empty thedate-released:
field. - In
codemeta.json
set"version": "0.0.0"
and"dateModified": ""
. - Prepare the
[Unreleased]
section inCHANGELOG.md
. Update the URL for[Unreleased]
in the link list of the bottom; it should comparedevelop
with the latest release.
- Set
- Check that Zenodo and Read the Docs have received the latest version:
- Zenodo: https://doi.org/10.5281/zenodo.4710254
- Read the Docs: https://modular-megafauna-model.readthedocs.io/en/latest/
- If applicable: Close the Milestone for this release on GitHub.
- Announce the release in the Matrix channel.
Follow Chris Beams’ guide for crafting your Git commit messages: How to Write a Git Commit Message
- Separate subject from body with a blank line
- Limit the subject line to 50 characters
- Capitalize the subject line
- Do not end the subject line with a period
- Use the imperative mood in the subject line
- Wrap the body at 72 characters
- Use the body to explain what and why vs. how
The repository should always be in a valid state.
A number of tests are defined in the file .gitlab-ci.yml
.
This file works with GitLab Continuous Integration (CI).
The CI script also runs Valgrind memory check.
With the bash script tools/run_valgrind_memcheck
you can execute a memory check manually on your local machine.
Always make sure contributions to the codebase don’t have memory leaks.
- Familiarize yourself with the REUSE standard in this tutorial: https://reuse.software/tutorial/
- When you create a new file, add a REUSE license header with the same license as similar files in the project.
- When you contribute to a file, add yourself as a copyright holder to the REUSE license header.
- When you create a commit with Git, use the
-s/--signoff
flag in order to sign the Developer Certificate of Origin. This way you certify that you wrote or otherwise have the right to submit the code you’re contributing to the project.- Just come into the habit of writing
git commit -s
.
- Just come into the habit of writing
This project follows the Pitchfork Layout for C++ projects. Here is a summary of the relevant parts:
- Namespace Folders: The
src/
directory has subfolders reflecting the namespaces of the contained components. To minimize the danger of name collision, the header include guards contain the namespace hierarchy also, e.g.FAUNA_OUTPUT_HABITAT_DATA_H
. - Separate Header Placement: Header (
*.h
) and source (*.cpp
) files are kept together insrc/
if they are private (not part of the library interface). Public headers are placed ininclude/
while their corresponding source files remain insrc/
. - Coherence: A header and corresponding source file (= physical component)contain code for logical component. If in doubt, rather air on the side of granularity and create several individual components.
- Merged Test Placement: Any logical/physical unit has its unit test in a file in the same folder with the same file name, but with the suffix
.test.cpp
.
The C++ code layout follows the Google C++ Style Guide. No worries, you don’t need to read everything. Perhaps your IDE or text editor cat automatically format your code using Clang in Google style.
Alternatively, do the code formatting automatically in a Git pre-commit by adding this line to .git/hooks/pre-commit
in each of your working copies:
#!/bin/bash
git-clang-format --extensions cpp,h --style Google --staged --quiet
Make sure to make it executable: chmod +x .git/hooks/pre-commit
.
The command git-clang-format
may be already installed with Clang, otherwise install it separately for your platform.
Do the code formatting automatically in a Git pre-commit by adding this line to .git/hooks/pre-commit
in each of your working copies:
#!/bin/bash
git-clang-format --quiet
Make sure to make it executable: chmod +x .git/hooks/pre-commit
.
The command git-clang-format
may be already installed with Clang, otherwise install it separately for your platform.
This repository has a .editorconfig file to define indentation rule for different file types. Please install the plugin for your text editor if available: editorconfig.org/
- Files are always lower-case with underscores to separate words.
- Header files end with
.h
, source files with.cpp
, and the corresponding unit test files with.test.cpp
. - If a file only contains one class, name the file like the class.
- If a file contains several classes, use a plural like
net_energy_models.h
. - If a file contains a collection of functionality, use an abstract grouping noun, e.g.
stochasticity.h
ornitrogen.h
.
- Header files end with
- Classes are named in CamelCase with upper-case first letter, e.g.
MyExampleClass
. Don’t repeat the namespace in the class name (avoid something likeOutput::OutputDataClass
).- Enum types are like classes.
- Functions are imperative verbs with underscores, e.g.
create_new_herbivores()
. - Global constants as well as static const member and function variables are all-uppercase with underscores, e.g.
MY_GLOBAL_CONSTANT
.- C++11-style enum class elements don’t have global scope and thus don’t require a prefix. Since the shouting tone of all-uppercase names is distracting, just use CamelCase for the enum members, e.g.
OutputInterval::Annual
.
- C++11-style enum class elements don’t have global scope and thus don’t require a prefix. Since the shouting tone of all-uppercase names is distracting, just use CamelCase for the enum members, e.g.
- Namespaces are short and lower-case with first letter capitalized, e.g.
Fauna
.
An example class definition in a header file:
class MyExampleClass{
public: // -> public members first
/// Constructor
MyExampleClass(); // -> constructors are always first
/// Destructor
~MyExampleClass(); // -> destructor comes next
// -> The following member functions in alphabetical order:
/// Create new herbivore instance.
Herbivore* create_herbivore();
/// Get the type of herbivore.
HerbivoreType get_herbivore_type();
/// A public global constant in this class.
static const int MY_CONSTANT = 10;
private:
/// Helper function to perform calculations.
void my_private_function();
// -> Finally the private member variables NOT in alphabetical order, but in
// the order of desired initialization.
HerbivoreType herbi_type;
int var = 10;
};
In the corresponding source file, all function definitions (both private & public) are in alphabetical order, except for the constructors and destructor, which come first.
If there is more than one class in the header file, separate their function definitions in blocks with big comment captions, for example like this:
//============================================================
// HftPopulationsMap
//============================================================
If there are functions local to this file, put them in an anonymous namespace before any other definitions:
namespace {
int my_local_function(){
// ...
};
}
Begin each .h
or .cpp
file with a doxygen header containing a brief description.
The description will appear in the file list of the generated doxygen documentation.
Ususally the brief description will be the same for a .h
and its .cpp
file.
If it is only one class in the header file, you can also copy the \brief
description from that class.
Here is an example:
/**
* \file
* \brief Management classes of herbivore populations.
* \copyright LGPL-3.0-or-later
* \date <current year>
*/
We omit the \author
field because it might be difficult to keep track of all authors who have contributed.
(That’s what version control is for.)
Instead, all contributors of the project shall be listed collectively in the “Authors” section of the README.md
.
The \date
field is only relevant for the copyright. (Use Git to see when the file has been changed.)
Make sure to write a unit test for every logical component.
If you create a .cpp
file, there should most likely also be a corresponding .test.cpp
file that checks the public functions of the class or classes.
Unit tests use the Catch2 framework in the single header distribution.
(The tests/catch.hpp
file can be updated from time to time.)
To run the unit tests after building the megafauna library, run ./megafauna_unit_tests
in the build directory.
To disable building the unit tests, you can call cmake -DBUILD_TESTING=OFF /path/to/repo
.
Run cppclean on the code to find unnecessary #include
s, unnecessary functions, and a lot more.
Execute the helper script ./tools/cppclean.sh
in the Bash.
- The documentation is completely in English, preferably with American spelling.
- Images are placed in
docs/images/
. If the figure was plotted with a script, save the script in the same folder and with the same file name as the image.
- Overview pages are written in Markdown in
*.md
-files in thedocs/
folder. - Make a new line after each sentence (and perhaps after logical sentence structures): Inner Sentence Rule.
BibTeX is used for bibliographic references: docs/bibliography.bib.
The Doxygen command \cite
is used for that.
This makes browsing the Doxygen documentation easier.
In general you should not need to put any references to scientific publications in comments in the source code.
Better you explain everything in a narrative form in the Doxygen documentation and use the \cite
command for that.
If you do cite in source code comments, make sure that the reference is uniquely identifiable in bibliography.bib
.
Write the in-text citation in the doxygen documentation (either in a C++ file or in a Markdown document) in the APA format followed by the \cite
reference:
Illius & O’Connor (2000) \cite illius2000resource states that ...
Blaxter (1989, p. 123) \cite blaxter1989energy states that ...
(McDonald et al. 2010, p. 123 \cite mcdonald2010animal)
Use bibsort to sort the bibliography entries by label. Or you can do it manually, too. A sorted bibliography is easier to read and better to maintain with version control software.
- Be parsimonious! Some fields are not handled by Doxygen.
- Include the abstract whenever possible.
- Don’t use
journaltitle
anddate
. They are not recognized by Doxygen. Useyear
,month
, etc. instead ofdate
. - Use
{...}
brackets instead of""
. - Have equal signs (
=
) line up vertically (for prettiness). - BibTeX identifiers (inspired by the BibTeX export of Google Scholar):
<author><year><firstword>
(all lowercase and without delimiter)- author: Family name of first author as it would be cited (including van/von/…)
- year: Publication year.
- firstword: First word of the title excluding ‘the’, ‘a’, ‘an’, ‘of’, ‘is’, ‘are’, ‘were’, and the like. Hyphens, dashes, apostrophes, and slashes within the first (compound) word are simply omitted.
- If the above produces non-unique IDs, use the second word, or (if even that fails) the third.