Skip to content

Conversation

@renecannao
Copy link

@renecannao renecannao commented Jan 21, 2026

Summary

This PR modernizes the cpp-dotenv project to build with contemporary C++ toolchains (C++17, Clang 17+, modern CMake).

Changes

  • C++17 upgrade: Changed from C++11 to C++17 standard
  • CMake 3.16: Updated minimum CMake version from 3.10/3.0 to 3.16
  • Logger fix: Added const qualifier to logger::position::less::operator() for modern libc++ compatibility
  • Naming conflict fix: Resolved the namespace/class name ambiguity (both named dotenv) using C++17 inline variables

Technical Details

The dotenv class name matches its namespace, which causes C++ parser ambiguity when defining out-of-line members. This is fixed by:

  • Using C++17 inline variables for static member definitions
  • Adding a typedef alias in the .cpp file to disambiguate the class type

Test Plan

  • Project compiles successfully with Clang 17
  • Static library libcpp_dotenv.a builds without errors
  • All existing functionality preserved

Summary by CodeRabbit

  • New Features

    • Option to build both static and shared library variants, with backward-compatible aliasing.
  • Documentation

    • Require CMake ≥ 3.16 and C++17; expanded Build Options to document library choices and outputs.
  • Bug Fixes

    • Comparator updated to be const-correct for standard containers.
  • Refactor

    • Simplified library initialization and default dotenv path now ".env"; modernized build visibility and build-time options.

✏️ Tip: You can customize this high-level summary in your review settings.

Modernize the project to build with contemporary toolchains:

- Upgrade C++ standard from C++11 to C++17
- Update CMake minimum version to 3.16
- Fix logger comparator const-correctness for modern libc++
- Resolve namespace/class name ambiguity using inline variables
- Use typedef alias to disambiguate out-of-line member definitions

The dotenv class name matches its namespace, which causes parsing
issues in modern C++. Fixed by using C++17 inline variables for
static members and a typedef alias in the implementation.
Add a new CMake option BUILD_BOTH_LIBRARIES that allows building both
static and shared libraries in a single build directory.

This provides an alternative to the existing approach of building in
separate directories with BUILD_SHARED_LIBS=ON/OFF, while maintaining
backward compatibility with existing build scripts.

Usage:
  cmake .. -DBUILD_BOTH_LIBRARIES=ON

Produces:
  - libcpp_dotenv_static.a
  - libcpp_dotenv_shared.so (or .dylib on macOS)
@renecannao
Copy link
Author

@coderabbitai review

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR modernizes the cpp-dotenv project to support C++17 and Clang 17+ by updating the C++ standard, CMake configuration, and fixing compatibility issues with modern toolchains.

Changes:

  • Upgraded from C++11 to C++17 standard across all CMakeLists.txt files
  • Added const qualifier to logger comparator for modern libc++ compatibility
  • Introduced C++17 inline variables to resolve namespace/class name collision

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
CMakeLists.txt Updated minimum CMake version to 3.16 and C++ standard to 17
common/libs/antlr4-cpp-runtime/CMakeLists.txt Updated CMake version to 3.10 and C++ standard to 17
include/dotenv.h Added inline static member definitions and changed default parameter from env_filename to literal
src/dotenv.cpp Added typedef workaround and fully qualified names to resolve namespace/class collision
src/common/logger.h Added const qualifier to comparator operator() declaration
src/common/logger.cpp Added const qualifier to comparator operator() implementation

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +47 to +48
inline const std::string dotenv::env_filename = ".env";
inline dotenv dotenv::_instance;
Copy link

Copilot AI Jan 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The inline variable definitions on lines 47-48 conflict with the existing static member declarations on lines 38-39 (which are still in the class). When using C++17 inline variables, the static member declarations inside the class should be removed. Currently, this creates duplicate definitions that violate the One Definition Rule and will cause linker errors.

Copilot uses AI. Check for mistakes.
src/dotenv.cpp Outdated


const dotenv::dotenv::value_type dotenv::dotenv::operator[](const key_type& k) const
auto ::dotenv::dotenv::operator[](const key_type& k) const -> const string
Copy link

Copilot AI Jan 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The return type declaration uses a trailing return type syntax auto ... -> const string, but this is inconsistent with the header declaration which uses const value_type. While value_type is a typedef for std::string, using the auto with trailing return type is unnecessary here and creates a mismatch in style. Consider using the same return type as declared in the header: const value_type or const string.

Suggested change
auto ::dotenv::dotenv::operator[](const key_type& k) const -> const string
const value_type ::dotenv::dotenv::operator[](const key_type& k) const

Copilot uses AI. Check for mistakes.
src/dotenv.cpp Outdated
dotenv::dotenv& dotenv::dotenv::load_dotenv(const string& dotenv_path, const bool overwrite, const bool interpolate)
// Typedef to work around the fact that class name matches namespace name
// This is a known C++ limitation when defining out-of-line members
typedef class ::dotenv::dotenv Dotenv_Type;
Copy link

Copilot AI Jan 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The typedef Dotenv_Type uses an unconventional naming style (Pascal_Snake_Case) that doesn't match typical C++ naming conventions. Consider using either DotenvType (PascalCase) or dotenv_type (snake_case) for better consistency with common C++ style guides.

Copilot uses AI. Check for mistakes.
@@ -1,7 +1,7 @@
cmake_minimum_required(VERSION 3.0.2)
cmake_minimum_required(VERSION 3.10)
Copy link

Copilot AI Jan 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The CMake minimum version is set to 3.10, but the main CMakeLists.txt now requires 3.16. This creates an inconsistency where a subdirectory has a lower minimum version than the parent project. Consider updating this to match the parent project's requirement of 3.16 for consistency, or ensure this subdirectory can genuinely build with just 3.10.

Suggested change
cmake_minimum_required(VERSION 3.10)
cmake_minimum_required(VERSION 3.16)

Copilot uses AI. Check for mistakes.
- Add validation to prevent BUILD_BOTH_LIBRARIES and BUILD_SHARED_LIBS
  from being used together (with warning message)
- Improve documentation for the cpp_dotenv alias, explaining that it
  points to cpp_dotenv_static when BUILD_BOTH_LIBRARIES is ON
- Add OUTPUT_NAME property so libraries are named correctly:
  * libcpp_dotenv.a (static) instead of libcpp_dotenv_static.a
  * libcpp_dotenv.so/.dylib (shared) instead of libcpp_dotenv_shared.so
- Enable CMAKE_POSITION_INDEPENDENT_CODE when building shared libraries
  to ensure static dependencies are compiled with -fPIC
- Document BUILD_BOTH_LIBRARIES option in README.md with usage examples

The OUTPUT_NAME property sets only the base name; CMake automatically
adds the correct platform-specific extension (.so on Linux, .dylib on macOS).
- Fix README CMake version mismatch: update from >=3.10 to >=3.16
  to match CMakeLists.txt requirement
- Fix Windows MSVC import library conflict: when BUILD_BOTH_LIBRARIES is ON,
  both static and shared libraries would produce cpp_dotenv.lib on Windows.
  Add IMPORT_SUFFIX "_shared.lib" to the shared library's import library
  to avoid this conflict on Windows/MSVC.
Add BUILD_BOTH_LIBRARIES option for TAP tests
@coderabbitai
Copy link

coderabbitai bot commented Jan 21, 2026

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

📝 Walkthrough

Walkthrough

Upgrades CMake (≥3.16) and C++ standard (→17), adds BUILD_BOTH_LIBRARIES to produce static and shared targets (with aliasing and PIC/validation), and applies C++17 inline/static-member changes plus const-correctness and minor implementation adjustments.

Changes

Cohort / File(s) Summary
Build system
CMakeLists.txt, common/libs/antlr4-cpp-runtime/CMakeLists.txt
Bumped minimum CMake (→3.16) and C++ standard (→17). Added BUILD_BOTH_LIBRARIES option, PIC handling, validation/warnings, created cpp_dotenv_static and cpp_dotenv_shared (MSVC output-name special-case), and an alias cpp_dotenv → static when both built. Centralized public include dirs and compile options.
Documentation
README.md
Updated CMake minimum and added "Build Options" describing BUILD_SHARED_LIBS, BUILD_BOTH_LIBRARIES, produced targets (cpp_dotenv_static, cpp_dotenv_shared), output names, and alias behavior; wording adjustments.
Public headers
include/dotenv.h
Default load_dotenv path changed to literal ".env". Introduced C++17 inline definitions for static members (env_filename, _instance) outside the class.
Logger interface
src/common/logger.h
Made logger::position::less::operator() const-qualified.
Implementations
src/dotenv.cpp, src/common/logger.cpp
Added typedef class ::dotenv::dotenv DotenvClass workaround; updated out-of-line definitions (load_dotenv, operator[], instance) to use the alias and inline-initialization pattern; implemented const-qualified comparator in logger.cpp.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐰 I hopped through CMake and found a new way,
Two libraries sprout at the break of day.
Inline variables snug in their nest,
Const-correctness helps the code run best,
I nibble a carrot and cheer — hooray! 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Fix build for modern C++17 and Clang 17' directly and specifically describes the main objective of the PR—modernizing the build system to support modern C++ (C++17) and contemporary compilers (Clang 17), which aligns with the core changes made across CMakeLists.txt, header files, and source files.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 6 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

CMakeLists.txt Outdated
Comment on lines 46 to 49
if ("${CMAKE_BUILD_TYPE}" STREQUAL "DEBUG")
set(CPP_DOTENV_COMPILE_OPTIONS -g -Wall -O0)
else()
set(CPP_DOTENV_COMPILE_OPTIONS -O3)
Copy link

Copilot AI Jan 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The compile options are hardcoded for GCC/Clang-style flags (-g, -Wall, -O0, -O3). These flags won't work correctly with MSVC on Windows. Since the code includes MSVC-specific logic for import libraries (lines 77-79), the build system should also handle MSVC-specific compiler flags.

Consider using generator expressions or checking the compiler type to set appropriate flags for each compiler. For example:

  • MSVC uses /W4 instead of -Wall, /Od instead of -O0, /O2 instead of -O3
  • Or use CMake's built-in CMAKE_CXX_FLAGS_DEBUG and CMAKE_CXX_FLAGS_RELEASE which handle compiler differences automatically
Suggested change
if ("${CMAKE_BUILD_TYPE}" STREQUAL "DEBUG")
set(CPP_DOTENV_COMPILE_OPTIONS -g -Wall -O0)
else()
set(CPP_DOTENV_COMPILE_OPTIONS -O3)
if (MSVC)
# MSVC-specific flags
if ("${CMAKE_BUILD_TYPE}" STREQUAL "DEBUG")
set(CPP_DOTENV_COMPILE_OPTIONS /W4 /Od)
else()
set(CPP_DOTENV_COMPILE_OPTIONS /W4 /O2)
endif()
else()
# GCC/Clang and other compilers using Unix-style flags
if ("${CMAKE_BUILD_TYPE}" STREQUAL "DEBUG")
set(CPP_DOTENV_COMPILE_OPTIONS -g -Wall -O0)
else()
set(CPP_DOTENV_COMPILE_OPTIONS -O3)
endif()

Copilot uses AI. Check for mistakes.
CMakeLists.txt Outdated
Comment on lines 73 to 78
# Set output name to libcpp_dotenv.so/.dylib (instead of libcpp_dotenv_shared.so)
set_target_properties(cpp_dotenv_shared PROPERTIES OUTPUT_NAME cpp_dotenv)
# On Windows (MSVC), the import library would conflict with the static library.
# Add a suffix to the import library name to avoid this conflict.
if(MSVC)
set_target_properties(cpp_dotenv_shared PROPERTIES IMPORT_SUFFIX "_shared.lib")
Copy link

Copilot AI Jan 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When BUILD_BOTH_LIBRARIES is ON, both cpp_dotenv_static and cpp_dotenv_shared are configured to use the same OUTPUT_NAME "cpp_dotenv" (lines 63 and 74). This means both libraries will try to create files with the same base name in the same directory:

  • Static library: libcpp_dotenv.a (or cpp_dotenv.lib on Windows)
  • Shared library: libcpp_dotenv.so/.dylib (or cpp_dotenv.dll on Windows)

While this works on Unix-like systems where static and shared libraries have different extensions (.a vs .so/.dylib), on Windows with MSVC, both the static library and the import library for the shared library would have the .lib extension, causing a file conflict. The IMPORT_SUFFIX workaround on lines 77-79 only addresses the import library suffix but doesn't prevent the base name conflict for the actual static library file.

Consider using different output names when BUILD_BOTH_LIBRARIES is enabled, or documenting that this configuration is not fully supported on Windows with MSVC.

Suggested change
# Set output name to libcpp_dotenv.so/.dylib (instead of libcpp_dotenv_shared.so)
set_target_properties(cpp_dotenv_shared PROPERTIES OUTPUT_NAME cpp_dotenv)
# On Windows (MSVC), the import library would conflict with the static library.
# Add a suffix to the import library name to avoid this conflict.
if(MSVC)
set_target_properties(cpp_dotenv_shared PROPERTIES IMPORT_SUFFIX "_shared.lib")
# Set output name. On non-MSVC platforms, both static and shared libraries use
# the same base name "cpp_dotenv". On MSVC, give the shared library a distinct
# base name to avoid conflicts between the static library (.lib) and the
# import library for the shared library (also .lib).
if(MSVC)
set_target_properties(cpp_dotenv_shared PROPERTIES OUTPUT_NAME cpp_dotenv_shared)
else()
set_target_properties(cpp_dotenv_shared PROPERTIES OUTPUT_NAME cpp_dotenv)

Copilot uses AI. Check for mistakes.
public:

dotenv& load_dotenv(const std::string& dotenv_path = env_filename,
dotenv& load_dotenv(const std::string& dotenv_path = ".env",
Copy link

Copilot AI Jan 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The default parameter has been changed from env_filename (a static member variable) to the string literal ".env". While this resolves the compilation issue and the values are equivalent, this change creates code duplication - the same string literal ".env" now appears in two places (here and in the inline variable definition on line 47).

If the default filename ever needs to change, it would need to be updated in both locations. Consider whether the static member env_filename is still necessary, or if it should be removed entirely in favor of using the literal directly in both places. Alternatively, the function signature could reference the static member once the inline variable syntax is corrected.

Suggested change
dotenv& load_dotenv(const std::string& dotenv_path = ".env",
dotenv& load_dotenv(const std::string& dotenv_path = env_filename,

Copilot uses AI. Check for mistakes.
Comment on lines +47 to +48
inline const std::string dotenv::env_filename = ".env";
inline dotenv dotenv::_instance;
Copy link

Copilot AI Jan 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The inline variable declarations for static members appear outside the class definition. According to C++17 rules, when using the inline keyword for static data members, they should still be declared (not defined) inside the class. The definitions with inline should either be inside the class body or be marked as inline at their declaration point within the class.

The current approach declares these members as non-inline static members within the class (lines 38-39) but then attempts to define them as inline outside the class. This creates a mismatch. For proper C++17 inline variables, the declaration inside the class should include the inline keyword and the initialization, like:

static inline const std::string env_filename = ".env";
static inline dotenv _instance;

Then these lines 47-48 would not be needed at all.

Copilot uses AI. Check for mistakes.
src/dotenv.cpp Outdated
dotenv::dotenv& dotenv::dotenv::load_dotenv(const string& dotenv_path, const bool overwrite, const bool interpolate)
// Typedef to work around the fact that class name matches namespace name
// This is a known C++ limitation when defining out-of-line members
typedef class ::dotenv::dotenv Dotenv_Type;
Copy link

Copilot AI Jan 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The typedef uses inconsistent naming convention. In C++ projects, type aliases are typically named in PascalCase or snake_case matching the project's style. The name Dotenv_Type mixes conventions (capitalized first word with underscore separator).

Consider using a more conventional name like DotenvClass or dotenv_class to match the project's naming patterns. Additionally, since this is just a workaround for the namespace/class name conflict, a clearer name like DotenvImpl might better communicate its purpose.

Copilot uses AI. Check for mistakes.
src/dotenv.cpp Outdated
dotenv::dotenv dotenv::dotenv::_instance;

dotenv::dotenv& dotenv::env = dotenv::instance();
// Static members are now inline in the header (C++17)
Copy link

Copilot AI Jan 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With the C++17 inline variable approach used in the header file (lines 47-48 of dotenv.h), the static member variables env_filename and _instance are already defined in the header. The comment on line 46 states "Static members are now inline in the header (C++17)", which means the definitions of these members should not exist in the .cpp file at all.

However, this line defines env as a separate extern variable (not a static member). This is correct and should remain, but the comment is misleading as it suggests static members are being moved to inline definitions, when in fact only env (which is not a static member) is being defined here.

Suggested change
// Static members are now inline in the header (C++17)
// Define the global 'env' instance; static members are inline in the header (C++17)

Copilot uses AI. Check for mistakes.
dotenv.h:
- Clarify inline variable syntax with explanatory comment
- Add comment explaining why _instance cannot be inline inside class

dotenv.cpp:
- Rename Dotenv_Type to DotenvClass for better C++ conventions
- Fix operator[] to use typedef alias consistently (avoid parsing issues)
- Improve comment clarity for the global env variable definition

CMakeLists.txt:
- Add MSVC support for compiler flags (/W4 /Od /O2)
- Improve Windows library naming: use distinct OUTPUT_NAME on MSVC
  to avoid conflicts between static and shared library .lib files

common/libs/antlr4-cpp-runtime/CMakeLists.txt:
- Update cmake_minimum_required from 3.10 to 3.16 for consistency
@renecannao
Copy link
Author

@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented Jan 21, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.


private:

// Declare static members (definitions are inline after the class)
Copy link

Copilot AI Jan 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The static member env_filename is declared and defined (line 47) but appears to be unused after changing the default parameter from env_filename to ".env" on line 18. Consider removing this static member unless it's being kept for backward compatibility with external code that may reference dotenv::dotenv::env_filename. If it's intentionally kept for backward compatibility, add a comment explaining this.

Suggested change
// Declare static members (definitions are inline after the class)
// Declare static members (definitions are inline after the class)
// NOTE: env_filename is kept for backward compatibility; external code
// may reference dotenv::dotenv::env_filename directly.

Copilot uses AI. Check for mistakes.
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Address remaining Copilot feedback: env_filename is kept for backward
compatibility as external code may reference dotenv::dotenv::env_filename
directly. Add explanatory comment to document this.
@renecannao renecannao merged commit 405b5ab into main Jan 21, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants