adding cpp example
This commit is contained in:
@@ -1,2 +1,2 @@
|
||||
add_subdirectory(example_c)
|
||||
# add_subdirectory(example_cpp)
|
||||
add_subdirectory(example_cpp)
|
||||
|
||||
@@ -10,6 +10,6 @@
|
||||
#define EXAMPLE_C_VERSION_MINOR 2
|
||||
#define EXAMPLE_C_VERSION_PATCH 3
|
||||
#define EXAMPLE_C_COMMIT_HASH cd01d7d8d36862bc00ae88f7cd185c352a7b7eda
|
||||
#define EXAMPLE_C_BUILD_TIMESTAMP 1729811627
|
||||
#define EXAMPLE_C_BUILD_TIMESTAMP 1729814020
|
||||
|
||||
#endif // INCLUDE_EXAMPLE_C_VERSION_H_
|
||||
|
||||
27
src/example_cpp/CMakeLists.txt
Normal file
27
src/example_cpp/CMakeLists.txt
Normal file
@@ -0,0 +1,27 @@
|
||||
add_executable(example_cpp main.cpp)
|
||||
execute_process(COMMAND ${PROJECT_SOURCE_DIR}/scripts/versioning.sh WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
|
||||
OUTPUT_VARIABLE EXAMPLE_CPP_VERSION)
|
||||
|
||||
message(STATUS "Building [example_cpp] version ${EXAMPLE_CPP_VERSION}")
|
||||
target_compile_options(example_cpp PRIVATE "${COMPILE_FLAGS}")
|
||||
target_link_options(example_cpp PRIVATE "${LINK_FLAGS}")
|
||||
|
||||
# Check if compiler supports C++11
|
||||
target_compile_features(example_cpp PUBLIC cxx_std_11)
|
||||
target_link_libraries(example_cpp plog::plog)
|
||||
|
||||
file(GLOB CPP_FILES CONFIGURE_DEPENDS *.cpp)
|
||||
file(GLOB HPP_FILES CONFIGURE_DEPENDS *.hpp)
|
||||
|
||||
target_sources(example_cpp PRIVATE ${CPP_FILES} PUBLIC ${HPP_FILES})
|
||||
target_include_directories(example_cpp PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
|
||||
target_link_libraries(example_cpp backtrace)
|
||||
if(STATIC_ARGPARSE)
|
||||
target_link_libraries(example_cpp argparse_static)
|
||||
else()
|
||||
target_link_libraries(example_cpp argparse_shared)
|
||||
endif()
|
||||
|
||||
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/argparser)
|
||||
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/backtracer)
|
||||
9
src/example_cpp/argparser/CMakeLists.txt
Normal file
9
src/example_cpp/argparser/CMakeLists.txt
Normal file
@@ -0,0 +1,9 @@
|
||||
file(GLOB CPP_FILES CONFIGURE_DEPENDS *.cpp)
|
||||
file(GLOB HPP_FILES CONFIGURE_DEPENDS *.hpp)
|
||||
|
||||
target_sources(
|
||||
example_cpp
|
||||
PRIVATE ${CPP_FILES}
|
||||
PUBLIC ${HPP_FILES})
|
||||
|
||||
target_include_directories(example_cpp PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
109
src/example_cpp/argparser/argparser.cpp
Normal file
109
src/example_cpp/argparser/argparser.cpp
Normal file
@@ -0,0 +1,109 @@
|
||||
#include "argparser.hpp"
|
||||
#include <algorithm>
|
||||
|
||||
ArgumentParser::~ArgumentParser() {
|
||||
for (size_t index = 0; index < options_.size(); index++) {
|
||||
auto opt = options_[index];
|
||||
|
||||
if (opt.type == ARGPARSE_OPT_END || std::string(opt.long_name) == "help")
|
||||
continue;
|
||||
|
||||
delete[] opt.long_name;
|
||||
delete[] opt.help;
|
||||
}
|
||||
}
|
||||
|
||||
bool ArgumentParser::parse(int argc, const char **argv) {
|
||||
std::vector<struct argparse_option> options = options_;
|
||||
options.push_back(OPT_END());
|
||||
bool rc = true;
|
||||
|
||||
std::string usage_str = application_name_ + " [options]";
|
||||
if (require_arguments_ && allowed_arguments_.empty())
|
||||
usage_str += " [args]";
|
||||
|
||||
if (!allowed_arguments_.empty()) {
|
||||
for (size_t i = 0; i < allowed_arguments_.size(); i++) {
|
||||
auto current_arg_list = allowed_arguments_.at(i);
|
||||
usage_str += " [";
|
||||
for (size_t j = 0; j < current_arg_list.size(); j++) {
|
||||
if (j != 0)
|
||||
usage_str += " | ";
|
||||
usage_str += current_arg_list.at(j);
|
||||
}
|
||||
usage_str += "]";
|
||||
}
|
||||
}
|
||||
|
||||
static const char *const usage[] = {usage_str.c_str(), NULL};
|
||||
|
||||
argparse_init(&argparse_, options.data(), usage, 0);
|
||||
argparse_describe(&argparse_, description_.c_str(), "");
|
||||
argc = argparse_parse(&argparse_, argc, argv);
|
||||
|
||||
if (argc == 0 && require_arguments_)
|
||||
rc = false;
|
||||
else {
|
||||
for (int i = 0; i < argc; i++) {
|
||||
std::string arg = std::string(argv[i]);
|
||||
arguments_.push_back(arg);
|
||||
bool arg_allowed = false;
|
||||
for (size_t i = 0; i < allowed_arguments_.size(); i++) {
|
||||
auto current_arg_list = allowed_arguments_.at(i);
|
||||
auto allowed_arg = std::find(current_arg_list.begin(), current_arg_list.end(), arg);
|
||||
if (allowed_arg != current_arg_list.end()) {
|
||||
arg_allowed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!arg_allowed)
|
||||
rc = false;
|
||||
}
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
std::vector<std::string> split(const std::string &str, char delimiter) {
|
||||
std::vector<std::string> tokens;
|
||||
std::string::size_type start = 0;
|
||||
std::string::size_type end = str.find(delimiter);
|
||||
|
||||
while (end != std::string::npos) {
|
||||
tokens.push_back(str.substr(start, end - start));
|
||||
start = end + 1;
|
||||
end = str.find(delimiter, start);
|
||||
}
|
||||
|
||||
tokens.push_back(str.substr(start));
|
||||
return tokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* This callback is used to process the value of an option string, which is
|
||||
* typically a key-value pair. It splits the input buffer into parts based on
|
||||
* the '=' character, and uses one part as the final output string.
|
||||
*/
|
||||
int ArgumentParser::opt_string_callback(struct argparse *parser, const struct argparse_option *flag) {
|
||||
std::string buffer(parser->optvalue);
|
||||
auto buffer_parts = split(buffer, '=');
|
||||
if (buffer_parts.size() > 1)
|
||||
buffer = buffer_parts[1];
|
||||
|
||||
std::string *dst_str = reinterpret_cast<std::string *>(flag->data);
|
||||
dst_str->clear();
|
||||
dst_str->append(buffer);
|
||||
|
||||
parser->optvalue = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* This callback is used to process the value of an option boolean, which is
|
||||
* typically a key only value.
|
||||
*/
|
||||
int ArgumentParser::opt_boolean_callback(struct argparse *parser, const struct argparse_option *flag) {
|
||||
bool *dst = reinterpret_cast<bool *>(flag->data);
|
||||
*dst = true;
|
||||
return 0;
|
||||
}
|
||||
163
src/example_cpp/argparser/argparser.hpp
Normal file
163
src/example_cpp/argparser/argparser.hpp
Normal file
@@ -0,0 +1,163 @@
|
||||
#ifndef INCLUDE_ARGPARSER_ARGPARSER_HPP_
|
||||
#define INCLUDE_ARGPARSER_ARGPARSER_HPP_
|
||||
|
||||
#include <argparse.h>
|
||||
#include <cstring>
|
||||
#include <plog/Log.h>
|
||||
#include <string>
|
||||
#include <typeinfo>
|
||||
#include <vector>
|
||||
|
||||
/**
|
||||
* @class ArgumentParser
|
||||
* @brief A class to handle command line arguments.
|
||||
*
|
||||
* This class provides methods to add, get and manage known arguments from the command line. It users
|
||||
* (Argparse)[https://github.com/cofyc/argparse] under the hood
|
||||
*/
|
||||
class ArgumentParser {
|
||||
public:
|
||||
/**
|
||||
* Constructor with application name, description and requirement of arguments.
|
||||
*
|
||||
* @param application_name Name of the application.
|
||||
* @param description Description of the application.
|
||||
* @param require_arguments Whether the application requires arguments or not.
|
||||
*/
|
||||
ArgumentParser(const std::string &application_name, const std::string &description, bool require_arguments = false,
|
||||
std::vector<std::vector<std::string>> allowed_arguments = {})
|
||||
: application_name_(application_name), description_(description), require_arguments_(require_arguments),
|
||||
allowed_arguments_(allowed_arguments) {};
|
||||
|
||||
/**
|
||||
* Destructor for ArgumentParser.
|
||||
*/
|
||||
~ArgumentParser();
|
||||
|
||||
/**
|
||||
* Adds a flag to the list of flags.
|
||||
*
|
||||
* @tparam T Type of the flag value (int, float, bool or std::string).
|
||||
* @param short_name Short name of the flag.
|
||||
* @param long_name Long name of the flag.
|
||||
* @param value Pointer to the flag value.
|
||||
* @param description Description of the flag.
|
||||
* \todo Support callbacks for flags as in
|
||||
* https://github.com/cofyc/argparse/blob/682d4520b4bc2b646cdfcf078b2fed00b3d2da30/tests/basic.c#L34
|
||||
* \todo Implement logic to allow only to choose from a specific set of choices for a variable
|
||||
*/
|
||||
template <typename T> void add_flag(char short_name, const std::string &long_name, T *value, const std::string &description) {
|
||||
const std::type_info &flag_type = typeid(T);
|
||||
|
||||
// Clone strings so we dont lose reference to it
|
||||
char *long_name_local = new char[long_name.size() + 1];
|
||||
std::strncpy(long_name_local, long_name.c_str(), long_name.size());
|
||||
long_name_local[long_name.size()] = '\0';
|
||||
PLOG_VERBOSE << "Adding flag " << long_name_local << " to the list of flags" << std::endl;
|
||||
|
||||
char *description_local = new char[description.size() + 1];
|
||||
std::strncpy(description_local, description.c_str(), description.size());
|
||||
description_local[description.size()] = '\0';
|
||||
|
||||
struct argparse_option flag_opt = {ARGPARSE_OPT_END, short_name, long_name_local, value, description_local, NULL, 0, 0};
|
||||
|
||||
if (flag_type == typeid(int)) {
|
||||
flag_opt.type = ARGPARSE_OPT_INTEGER;
|
||||
PLOG_VERBOSE << "Flag " << long_name_local << " is of type int" << std::endl;
|
||||
}
|
||||
|
||||
if (flag_type == typeid(uint)) {
|
||||
flag_opt.type = ARGPARSE_OPT_INTEGER;
|
||||
PLOG_VERBOSE << "Flag " << long_name_local << " is of type unsigned int" << std::endl;
|
||||
}
|
||||
|
||||
else if (flag_type == typeid(float)) {
|
||||
flag_opt.type = ARGPARSE_OPT_FLOAT;
|
||||
PLOG_VERBOSE << "Flag " << long_name_local << " is of type float" << std::endl;
|
||||
}
|
||||
|
||||
else if (flag_type == typeid(bool)) {
|
||||
flag_opt.type = ARGPARSE_OPT_BOOLEAN;
|
||||
flag_opt.value = NULL;
|
||||
flag_opt.callback = opt_boolean_callback;
|
||||
flag_opt.data = reinterpret_cast<intptr_t>(value);
|
||||
PLOG_VERBOSE << "Flag " << long_name_local << " is of type bool" << std::endl;
|
||||
}
|
||||
|
||||
else if (flag_type == typeid(std::string)) {
|
||||
flag_opt.type = ARGPARSE_OPT_STRING;
|
||||
flag_opt.value = NULL;
|
||||
flag_opt.callback = opt_string_callback;
|
||||
flag_opt.data = reinterpret_cast<intptr_t>(value);
|
||||
PLOG_VERBOSE << "Flag " << long_name_local << " is of type str" << std::endl;
|
||||
}
|
||||
|
||||
options_.push_back(flag_opt);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the command line arguments.
|
||||
*
|
||||
* @param argc Number of command line arguments, including binary name.
|
||||
* @param argv Array of command line arguments, including binary name at argv[0].
|
||||
* @return True if parsing is successful, false otherwise.
|
||||
*/
|
||||
bool parse(int argc, const char **argv);
|
||||
|
||||
/**
|
||||
* @brief Displays the usage message from argparse.
|
||||
*
|
||||
* This inline function calls `argparse_usage` with no arguments, printing the
|
||||
* usage message to stdout.
|
||||
*
|
||||
* @note The output is not redirected or buffered; it will be printed directly to the console.
|
||||
*/
|
||||
inline void show_usage() { argparse_usage(&argparse_); };
|
||||
|
||||
/**
|
||||
* @brief Returns a copy of the vector of command-line arguments.
|
||||
*
|
||||
* This method returns a copy of the internal `arguments_` vector, which is used to store
|
||||
* the command-line arguments passed to the program. The returned vector is not modified.
|
||||
*
|
||||
* @return A copy of the `arguments_` vector.
|
||||
*/
|
||||
inline std::vector<std::string> get_arguments() const { return arguments_; };
|
||||
|
||||
private:
|
||||
/**
|
||||
* Vector of argparse options. Stores all flags/arguments added through add_flag
|
||||
*/
|
||||
std::vector<struct argparse_option> options_ = {OPT_HELP()};
|
||||
|
||||
/**
|
||||
* List of command line arguments. Thing that are not flags, i.e. do not begin with -, will be stored here
|
||||
*/
|
||||
std::vector<std::string> arguments_;
|
||||
|
||||
std::string application_name_;
|
||||
std::string description_;
|
||||
bool require_arguments_;
|
||||
struct argparse argparse_;
|
||||
std::vector<std::vector<std::string>> allowed_arguments_;
|
||||
|
||||
/**
|
||||
* Callback function for string options.
|
||||
*
|
||||
* @param parser Argument parser object.
|
||||
* @param flag Argument option structure.
|
||||
* @return 0 on success, non-zero otherwise.
|
||||
*/
|
||||
static int opt_string_callback(struct argparse *, const struct argparse_option *);
|
||||
|
||||
/**
|
||||
* Callback function for boolean options.
|
||||
*
|
||||
* @param parser Argument parser object.
|
||||
* @param flag Argument option structure.
|
||||
* @return 0 on success, non-zero otherwise.
|
||||
*/
|
||||
static int opt_boolean_callback(struct argparse *, const struct argparse_option *);
|
||||
};
|
||||
|
||||
#endif // INCLUDE_ARGPARSER_ARGPARSER_HPP_
|
||||
9
src/example_cpp/backtracer/CMakeLists.txt
Normal file
9
src/example_cpp/backtracer/CMakeLists.txt
Normal file
@@ -0,0 +1,9 @@
|
||||
file(GLOB CPP_FILES CONFIGURE_DEPENDS *.cpp)
|
||||
file(GLOB HPP_FILES CONFIGURE_DEPENDS *.hpp)
|
||||
|
||||
target_sources(
|
||||
example_cpp
|
||||
PRIVATE ${CPP_FILES}
|
||||
PUBLIC ${HPP_FILES})
|
||||
|
||||
target_include_directories(example_cpp PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
108
src/example_cpp/backtracer/backtracer.cpp
Normal file
108
src/example_cpp/backtracer/backtracer.cpp
Normal file
@@ -0,0 +1,108 @@
|
||||
|
||||
#include "backtracer.hpp"
|
||||
#include "backtrace.h"
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <cxxabi.h>
|
||||
#include <iostream>
|
||||
#include <signal.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
Backtracer *Backtracer::instance = nullptr;
|
||||
|
||||
Backtracer *Backtracer::get_instance() {
|
||||
if (instance == nullptr)
|
||||
instance = new Backtracer();
|
||||
return instance;
|
||||
}
|
||||
|
||||
Backtracer::Backtracer() : backtrace_state(nullptr) { initialize_backtrace(); }
|
||||
Backtracer::~Backtracer() { delete instance; }
|
||||
|
||||
void Backtracer::initialize_backtrace() {
|
||||
backtrace_state = backtrace_create_state(nullptr, 0, backtrace_error_callback_create, nullptr);
|
||||
if (!backtrace_state) {
|
||||
std::cerr << "Failed to initialize backtrace engine" << std::endl;
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
|
||||
void Backtracer::capture_backtrace() {
|
||||
if (backtrace_state) {
|
||||
backtrace_full(backtrace_state, 0, backtrace_callback, backtrace_error_callback, nullptr);
|
||||
} else {
|
||||
std::cerr << "Backtrace state is not initialized" << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
void Backtracer::backtrace_error_callback_create(void *data, const char *msg, int errnum) {
|
||||
std::cerr << "Error " << errnum << "occurred when initializing the stacktrace: " << msg << std::endl;
|
||||
}
|
||||
|
||||
int Backtracer::backtrace_callback(void *data, uintptr_t pc, const char *filename, int lineno, const char *function) {
|
||||
if (function && filename) {
|
||||
std::cerr << "#" << reinterpret_cast<void *>(pc);
|
||||
std::cerr << " - " << demangle(function);
|
||||
std::cerr << " at " << filename << " (line " << lineno << ")" << std::endl;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Backtracer::backtrace_error_callback(void *data, const char *msg, int errnum) {
|
||||
std::cerr << "Error " << errnum << " occurred when getting the stacktrace: " << msg << std::endl;
|
||||
}
|
||||
|
||||
void Backtracer::signal_handler_callback(int signum) {
|
||||
printf("Error signal %s caught!\n", strsignal(signum));
|
||||
Backtracer::get_instance()->capture_backtrace();
|
||||
_exit(signum);
|
||||
}
|
||||
|
||||
void Backtracer::register_signal_handlers() {
|
||||
constexpr int n_signals = 7;
|
||||
|
||||
// Program Error Signals
|
||||
// https://www.gnu.org/software/libc/manual/html_node/Program-Error-Signals.html
|
||||
int signals_to_backtrace[n_signals] = {SIGFPE, SIGILL, SIGSEGV, SIGBUS, SIGABRT, SIGTRAP, SIGSYS};
|
||||
|
||||
// Blocking signals with set
|
||||
sigset_t block_mask;
|
||||
sigemptyset(&block_mask);
|
||||
sigprocmask(SIG_BLOCK, &block_mask, NULL);
|
||||
for (size_t i = 0; i < n_signals; i++) {
|
||||
sigaddset(&block_mask, signals_to_backtrace[i]);
|
||||
}
|
||||
|
||||
struct sigaction sigHandler;
|
||||
memset(&sigHandler, 0, sizeof(sigHandler));
|
||||
sigHandler.sa_handler = signal_handler_callback;
|
||||
sigHandler.sa_mask = block_mask;
|
||||
sigHandler.sa_flags = 0;
|
||||
for (size_t i = 0; i < n_signals; i++) {
|
||||
sigaction(signals_to_backtrace[i], &sigHandler, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
std::string Backtracer::demangle(const std::string &name) {
|
||||
int status = 0;
|
||||
char *demangledName = abi::__cxa_demangle(name.c_str(), nullptr, nullptr, &status);
|
||||
std::string result;
|
||||
|
||||
if (status == 0 && demangledName) {
|
||||
result = std::string(demangledName);
|
||||
free(demangledName);
|
||||
} else {
|
||||
result = name;
|
||||
}
|
||||
|
||||
if (result.empty() || result.back() != ')') {
|
||||
result += "()";
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
37
src/example_cpp/backtracer/backtracer.hpp
Normal file
37
src/example_cpp/backtracer/backtracer.hpp
Normal file
@@ -0,0 +1,37 @@
|
||||
#ifndef INCLUDE_BACKTRACER_BACKTRACER_HPP_
|
||||
#define INCLUDE_BACKTRACER_BACKTRACER_HPP_
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string>
|
||||
|
||||
class Backtracer {
|
||||
public:
|
||||
static Backtracer *get_instance();
|
||||
|
||||
void capture_backtrace();
|
||||
void register_signal_handlers();
|
||||
|
||||
private:
|
||||
static Backtracer *instance;
|
||||
|
||||
Backtracer();
|
||||
|
||||
~Backtracer();
|
||||
|
||||
// Deleted copy constructor and assignment operator
|
||||
Backtracer(const Backtracer &) = delete;
|
||||
Backtracer &operator=(const Backtracer &) = delete;
|
||||
|
||||
struct backtrace_state *backtrace_state;
|
||||
|
||||
static int backtrace_callback(void *data, uintptr_t pc, const char *filename, int lineno, const char *function);
|
||||
|
||||
static void backtrace_error_callback(void *data, const char *msg, int errnum);
|
||||
static void backtrace_error_callback_create(void *, const char *msg, int errnum);
|
||||
static void signal_handler_callback(int signum);
|
||||
static std::string demangle(const std::string& name);
|
||||
|
||||
void initialize_backtrace();
|
||||
};
|
||||
|
||||
#endif // INCLUDE_BACKTRACER_BACKTRACER_HPP_
|
||||
16
src/example_cpp/common.h
Normal file
16
src/example_cpp/common.h
Normal file
@@ -0,0 +1,16 @@
|
||||
#ifndef INCLUDE_SRC_COMMON_H_
|
||||
#define INCLUDE_SRC_COMMON_H_
|
||||
|
||||
#ifdef __GNUC__
|
||||
#define UNUSED(x) UNUSED_##x __attribute__((__unused__))
|
||||
#else
|
||||
#define UNUSED(x) UNUSED_##x
|
||||
#endif
|
||||
|
||||
#ifdef __GNUC__
|
||||
#define UNUSED_FUNCTION(x) __attribute__((__unused__)) UNUSED_##x
|
||||
#else
|
||||
#define UNUSED_FUNCTION(x) UNUSED_##x
|
||||
#endif
|
||||
|
||||
#endif // INCLUDE_SRC_COMMON_H_
|
||||
17
src/example_cpp/main.cpp
Normal file
17
src/example_cpp/main.cpp
Normal file
@@ -0,0 +1,17 @@
|
||||
#include "backtracer.hpp"
|
||||
#include "common.h"
|
||||
#include "version.hpp"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
void f3(char UNUSED(c) = 3) { abort(); }
|
||||
void f2(float b = 2) { f3(); }
|
||||
void f1(int a = 1) { f2(); }
|
||||
|
||||
|
||||
int main(int UNUSED(argc), char *UNUSED(argv[])) {
|
||||
Backtracer* backtracer = Backtracer::get_instance();
|
||||
backtracer->register_signal_handlers();
|
||||
printf("Example CPP Version %s\n", VERSION);
|
||||
f1();
|
||||
}
|
||||
15
src/example_cpp/version.hpp
Normal file
15
src/example_cpp/version.hpp
Normal file
@@ -0,0 +1,15 @@
|
||||
#ifndef INCLUDE_EXAMPLE_CPP_VERSION_H_
|
||||
#define INCLUDE_EXAMPLE_CPP_VERSION_H_
|
||||
|
||||
#define STRINGIFY(x) #x
|
||||
#define TOSTRING(x) STRINGIFY(x)
|
||||
|
||||
#define VERSION TOSTRING(EXAMPLE_CPP_VERSION_MAJOR) "." TOSTRING(EXAMPLE_CPP_VERSION_MINOR) "." TOSTRING(EXAMPLE_CPP_VERSION_PATCH)
|
||||
|
||||
#define EXAMPLE_CPP_VERSION_MAJOR 1
|
||||
#define EXAMPLE_CPP_VERSION_MINOR 2
|
||||
#define EXAMPLE_CPP_VERSION_PATCH 3
|
||||
#define EXAMPLE_CPP_COMMIT_HASH cd01d7d8d36862bc00ae88f7cd185c352a7b7eda
|
||||
#define EXAMPLE_CPP_BUILD_TIMESTAMP 1729814020
|
||||
|
||||
#endif // INCLUDE_EXAMPLE_CPP_VERSION_H_
|
||||
Reference in New Issue
Block a user