본문으로 건너뛰기
Previous
Next
Open Source in C++: From Reading Code to Your First Pull

Open Source in C++: From Reading Code to Your First Pull

Open Source in C++: From Reading Code to Your First Pull

이 글의 핵심

Contribute to C++ open source: choose the right project, fork and build locally, write your fix with tests, submit a clean PR, and survive code review. Practical examples using spdlog and fmt.

Why Contribute to Open Source?

Working on open source C++ libraries accelerates your skills in ways that proprietary codebases rarely do. You read code written by experienced engineers with different styles, submit to review by people with no obligation to be gentle, and learn how professional C++ projects handle CI, testing, and compatibility. A public PR record also matters when job-hunting — hiring managers can read your actual code.

The barrier is psychological, not technical. Most “good first issue” fixes take a few hours and require understanding only a small slice of the codebase.


Choosing the Right Project

Not every project is beginner-friendly. Look for:

SignalWhat to check
Active maintenanceRecent commits, issues responded to within days
CONTRIBUTING.mdDetailed build instructions, PR checklist, style guide
Good first issue labelsCurated entry points with clear scope
CI green on mainIf CI is already broken, your PR will fight noise
Reasonable review cultureSkim recent PR discussions for tone

Good C++ starting points:

ProjectWhy it’s beginner-friendly
spdlogSmall, well-tested logging library; clear code; welcoming maintainer
fmtHeader-heavy but modular; detailed issue descriptions
Catch2Test framework; contributors often start with test-writing PRs
nlohmann/jsonEnormous user base; many small, well-described issues
vcpkg portsAdding or updating a package port is self-contained and reviewed quickly

Avoid starting with: LLVM, GCC, Boost (large bureaucracy), or security-critical crypto libraries.


The Fork and Clone Workflow

Every open source contribution follows the same Git workflow:

# 1. Fork on GitHub (click Fork button on the project page)
#    This creates YOUR copy at github.com/YOUR_USERNAME/spdlog

# 2. Clone YOUR fork (not the upstream)
git clone https://github.com/YOUR_USERNAME/spdlog.git
cd spdlog

# 3. Add upstream remote so you can pull future changes
git remote add upstream https://github.com/gabime/spdlog.git

# Verify remotes
git remote -v
# origin    https://github.com/YOUR_USERNAME/spdlog.git (fetch)
# origin    https://github.com/YOUR_USERNAME/spdlog.git (push)
# upstream  https://github.com/gabime/spdlog.git (fetch)
# upstream  https://github.com/gabime/spdlog.git (push)

# 4. Fetch upstream to see what's on their main
git fetch upstream

Building and Running Tests

Before touching any code, get the project building and tests passing locally:

# spdlog CMake build
cmake -B build -DSPDLOG_BUILD_TESTS=ON -DCMAKE_BUILD_TYPE=Debug
cmake --build build -j$(nproc)
cd build && ctest --output-on-failure

# fmt CMake build
cmake -B build -DFMT_TEST=ON
cmake --build build -j$(nproc)
cd build && ctest --output-on-failure

If the build fails, check CONTRIBUTING.md for required dependencies. Common issues:

# Missing fmt dependency (spdlog often bundles it, but confirm)
git submodule update --init --recursive

# Compiler version mismatch
cmake -B build -DCMAKE_CXX_STANDARD=17 -DCMAKE_CXX_COMPILER=clang++

Run the full test suite before making any changes. You need a green baseline — if a test fails before your change, that is not your bug to fix (but you can note it).


Finding and Understanding the Issue

Once local builds are green, find an issue to work on:

# GitHub search for C++ good first issues
https://github.com/search?q=is:issue+is:open+label:"good+first+issue"+language:C%2B%2B

When you find a candidate:

  1. Read the issue fully — understand the expected vs. actual behavior
  2. Find the relevant code: git grep, find . -name "*.h", or use your IDE’s symbol search
  3. Reproduce the problem: write a tiny test.cpp that demonstrates the bug, or run the failing test
  4. Read the test file for the affected module — you will add a test alongside your fix
# In spdlog: find files related to "async" logging
grep -r "async_logger" include/ --include="*.h" -l
grep -r "async_logger" tests/  --include="*.cpp" -l

Creating Your Branch

Never work on main. Create a descriptive branch name:

# Sync your fork's main with upstream first
git fetch upstream
git checkout main
git merge upstream/main       # fast-forward if no local commits
git push origin main          # keep your fork's main in sync

# Create a topic branch
git checkout -b fix/async-logger-flush-on-destroy
# or
git checkout -b docs/add-sinks-example
# or
git checkout -b feat/add-daily-file-sink-rotation

Writing the Fix

A concrete example: suppose spdlog has a bug where the async logger does not flush on destruction. You find the destructor in include/spdlog/async_logger-inl.h:

// Before (broken)
SPDLOG_INLINE spdlog::async_logger::~async_logger() {
    // does nothing — messages in the queue may be lost
}
// After (fixed)
SPDLOG_INLINE spdlog::async_logger::~async_logger() {
    SPDLOG_TRY {
        flush();    // drain the queue before the thread pool shuts down
    }
    SPDLOG_LOGGER_CATCH(source_loc{})
}

Write a test that covers your fix:

// tests/test_async.cpp — add a new test case
TEST_CASE("async logger flushes on destruction", "[async]") {
    auto tp = std::make_shared<spdlog::details::thread_pool>(8, 1);
    auto sink = std::make_shared<spdlog::sinks::test_sink_mt>();
    {
        auto logger = std::make_shared<spdlog::async_logger>(
            "test", sink, tp, spdlog::async_overflow_policy::block);
        logger->info("message 1");
        logger->info("message 2");
        // destructor called here — should flush
    }
    // Messages must have reached the sink
    REQUIRE(sink->msg_counter() == 2);
}

Rebuild and run:

cmake --build build -j$(nproc)
cd build && ctest -R "async" --output-on-failure

Style and Formatting

Before committing, apply the project’s formatter:

# clang-format — most C++ projects use it
clang-format -i include/spdlog/async_logger-inl.h tests/test_async.cpp

# Check for issues without modifying
clang-format --dry-run --Werror file.cpp

# If the project has a script
./scripts/run-clang-format.sh

Check the .clang-format file in the root. Never override formatting choices — even if you disagree, a consistent style across the codebase is more important than your preference.


Committing with Conventional Commits and DCO

Most C++ projects use Conventional Commits for structured history:

type(scope): short imperative description

Longer explanation if needed.

Fixes #1234

Types: fix, feat, docs, refactor, test, perf, ci, chore

With DCO sign-off (-s):

git add include/spdlog/async_logger-inl.h tests/test_async.cpp
git commit -s -m "fix(async_logger): flush queue on destruction

Without an explicit flush() call in the destructor, messages buffered
in the async queue could be dropped when the logger went out of scope
before the thread pool finished processing them.

Fixes #789"

The -s flag appends a Signed-off-by: Your Name <[email protected]> line. Many projects check for this in CI and will reject PRs without it.


Opening the Pull Request

Push your branch:

git push -u origin fix/async-logger-flush-on-destroy

Then open a PR on GitHub. A good PR description:

## Summary

The async logger destructor did not call `flush()`, causing messages in
the async queue to be dropped when the logger went out of scope before
the thread pool finished processing them.

## Changes

- `async_logger::~async_logger()`: added `flush()` inside SPDLOG_TRY block
- `tests/test_async.cpp`: added test case `async logger flushes on destruction`

## How to test

```bash
cmake --build build && cd build && ctest -R "async" --output-on-failure

Fixes #789


Keep PRs **single-purpose**: one bug = one PR. Reviewers won't merge a PR that fixes a bug AND reorganizes unrelated code — it makes review harder and bisect messier.

---

## Surviving Code Review

Reviewers will ask for changes. Common feedback types:

**Style changes**: easy — just apply the requested format.

**Test coverage**: add more test cases to exercise edge cases.

**API questions**: the reviewer thinks your fix changes a public interface in a breaking way — discuss in the PR thread.

**Alternative approach**: the reviewer suggests a fundamentally different implementation. Ask why before rewriting — sometimes it is preference, sometimes it is a real constraint.

Responding:

```bash
# After making requested changes
git add -u
git commit -s -m "review: address feedback from @maintainer

- move flush() call inside SPDLOG_TRY block
- add test for overflow_policy::overrun_oldest behavior"

git push origin fix/async-logger-flush-on-destroy
# The PR updates automatically

Never force-push an open PR unless explicitly asked. It breaks review context.


Common Errors and Fixes

ErrorCauseFix
CMake can’t find dependencyMissing system packageCheck README; install with apt/brew/vcpkg
CI fails clang-formatFormatting differenceclang-format -i your changed files
CI fails on Windows but passes locallyPath separator, MSVC extensionTest on Windows or use GitHub Actions matrix
DCO check failsMissing Signed-off-by linegit commit --amend -s or git rebase --signoff
Merge conflict after rebaseUpstream moved while you workedgit fetch upstream && git rebase upstream/main
Test fails on CI but passes locallyEnvironment differenceCheck if test uses hardcoded paths, clocks, or OS APIs

What Happens After Your PR is Merged

  • Your commit SHA is now in the official history of a public library
  • You are listed in the contributors graph
  • You understand that slice of the codebase deeply — good for mentioning in interviews
  • You have a template for every future contribution to any project

The first merge is the hardest. After that, the workflow is muscle memory and the review process stops feeling intimidating.


Key Takeaways

  • Start small: documentation, test additions, and well-scoped bugfixes are ideal first PRs
  • Build before you touch anything — you need a green baseline to prove your change didn’t break existing tests
  • One issue per PR: maintainers won’t review a PR that does multiple things
  • Conventional Commits + DCO: git commit -s -m "fix(scope): ..." is the standard pattern in most C++ projects
  • clang-format before push: CI will catch it anyway — fix it locally first
  • Never force-push an open PR — it breaks review context
  • Rejections are normal: fold the feedback, pick another issue, keep going

자주 묻는 질문 (FAQ)

Q. 이 내용을 실무에서 언제 쓰나요?

A. Contribute to famous C++ libraries: pick issues, fork workflow, Conventional Commits, CI, DCO, and review culture. Pract… 실무에서는 위 본문의 예제와 선택 가이드를 참고해 적용하면 됩니다.

Q. 선행으로 읽으면 좋은 글은?

A. 각 글 하단의 이전 글 또는 관련 글 링크를 따라가면 순서대로 배울 수 있습니다. C++ 시리즈 목차에서 전체 흐름을 확인할 수 있습니다.

Q. 더 깊이 공부하려면?

A. cppreference와 해당 라이브러리 공식 문서를 참고하세요. 글 말미의 참고 자료 링크도 활용하면 좋습니다.


같이 보면 좋은 글 (내부 링크)

이 주제와 연결되는 다른 글입니다.


이 글에서 다루는 키워드 (관련 검색어)

C++, Open Source, Contribution, Pull Request, GitHub, spdlog, fmt 등으로 검색하시면 이 글이 도움이 됩니다.