commit f9c9207fc65c6511b9bef939444bfc088937180a Author: Romain BOULLARD Date: Mon Aug 17 01:05:55 2026 +0200 Initial commit diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..e93c306 --- /dev/null +++ b/.clang-format @@ -0,0 +1,122 @@ +--- +AlignAfterOpenBracket: Align +AlignArrayOfStructures: None +AccessModifierOffset: -2 +AlignConsecutiveBitFields: + Enabled: true + AcrossEmptyLines: false + AcrossComments: true + AlignCompound: true + PadOperators: true +AlignConsecutiveMacros: + Enabled: true + AcrossEmptyLines: false + AcrossComments: true + AlignCompound: true + PadOperators: true +AlignConsecutiveShortCaseStatements: + Enabled: true + AcrossEmptyLines: true + AcrossComments: true + AlignCaseColons: true +AlignEscapedNewlines: Right +AlignOperands: Align +AlignTrailingComments: + Kind: Always + OverEmptyLines: 2 +AllowAllArgumentsOnNextLine: false +AllowAllParametersOfDeclarationOnNextLine: false +AllowShortBlocksOnASingleLine: Empty +AllowShortCaseLabelsOnASingleLine: false +AllowShortEnumsOnASingleLine: false +AllowShortFunctionsOnASingleLine: Empty +AllowShortIfStatementsOnASingleLine: Never +AllowShortLambdasOnASingleLine: Empty +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: Yes +BinPackArguments: false +BinPackParameters: false +BitFieldColonSpacing: After +BreakBeforeBraces: Custom +BracedInitializerIndentWidth: 2 +BreakAfterAttributes: Always +BreakArrays: false +BreakBeforeTernaryOperators: true +BreakConstructorInitializers: AfterColon +BreakInheritanceList: BeforeComma +BreakStringLiterals: true +CompactNamespaces: false +Cpp11BracedListStyle: true +EmptyLineBeforeAccessModifier: Always +FixNamespaceComments: true +IncludeBlocks: Preserve +IndentCaseBlocks: true +IndentCaseLabels: false +IndentPPDirectives: None +IndentRequiresClause: true +IndentWidth: 4 +IndentWrappedFunctionNames: true +InsertBraces: true +InsertNewlineAtEOF: true +IntegerLiteralSeparator: + Binary: -1 + Decimal: 3 + DecimalMinDigits: 5 + Hex: -1 +LambdaBodyIndentation: Signature +Language: Cpp +NamespaceIndentation: None +PointerAlignment: Left +QualifierAlignment: Leave +ReferenceAlignment: Left +UseTab: Never +Standard: Auto +BraceWrapping: + AfterNamespace: true + AfterCaseLabel: true + AfterClass: true + AfterControlStatement: Always + AfterEnum: true + AfterFunction: true + AfterStruct: true + AfterUnion: true + BeforeElse: true + BeforeLambdaBody: true + BeforeWhile: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +ColumnLimit: 120 +SpaceAfterTemplateKeyword: false +SortIncludes: CaseSensitive +PenaltyReturnTypeOnItsOwnLine: 500 +SpaceBeforeAssignmentOperators: true +SpaceBeforeCaseColon: false +SpaceBeforeCtorInitializerColon: false +SpaceBeforeInheritanceColon: false +SpaceBeforeRangeBasedForLoopColon: false +SpaceBeforeSquareBrackets: false +SpacesInAngles: Never +SpacesInConditionalStatement: false +SpaceBeforeParens: ControlStatements +SpacesInContainerLiterals: false +SpacesInSquareBrackets: false +SpacesBeforeTrailingComments: 3 +PackConstructorInitializers: Never +SeparateDefinitionBlocks: Always +MaxEmptyLinesToKeep: 1 +BreakBeforeBinaryOperators: None +AlignConsecutiveAssignments: + Enabled: false + AcrossEmptyLines: false + AcrossComments: true + AlignCompound: true + PadOperators: true +SpaceAroundPointerQualifiers: Default +SpaceBeforeCpp11BracedList: true +SpaceInEmptyBlock: true +PenaltyBreakAssignment: 200 +KeepEmptyLinesAtTheStartOfBlocks: false \ No newline at end of file diff --git a/.clang-tidy b/.clang-tidy new file mode 100644 index 0000000..d28ed7a --- /dev/null +++ b/.clang-tidy @@ -0,0 +1,22 @@ +# .clang-tidy +--- +Checks: > + -*, + bugprone-*, + clang-analyzer-*, + cppcoreguidelines-*, + modernize-*, + performance-*, + readability-*, + portability-*, + + -modernize-use-trailing-return-type, + -readability-avoid-const-params-in-decls, + -cppcoreguidelines-macro-usage, + -cppcoreguidelines-avoid-do-while + +ExcludeHeaderFilterRegex: '_generated.*' + +CheckOptions: + - key: readability-implicit-bool-conversion.AllowPointerConditions + value: true \ No newline at end of file diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..5dc46e6 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,3 @@ +* text=auto eol=lf +*.{cmd,[cC][mM][dD]} text eol=crlf +*.{bat,[bB][aA][tT]} text eol=crlf \ No newline at end of file diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml new file mode 100644 index 0000000..3d3025e --- /dev/null +++ b/.gitea/workflows/ci.yml @@ -0,0 +1,59 @@ +name: Bin2CPP + +on: + push: + branches: + - '**' + workflow_dispatch: + +jobs: + build-and-test: + runs-on: ubuntu-latest + timeout-minutes: 120 + container: + image: git.romainboullard.com/bigfootdev/linuxcppbuilder:main + strategy: + matrix: + build_type: ["Debug", "RelWithDebInfo", "Release"] + unity_build: ["ON", "OFF"] + conan_profile: ["./ConanProfiles/clang", "./ConanProfiles/clang_asan"] + name: "Build & Test ${{ matrix.build_type }} with ${{ matrix.conan_profile }} (Unity Build: ${{ matrix.unity_build }})" + steps: + - name: Install Node.js + run: apt-get update && apt-get install -y nodejs + + - name: Checkout repository + uses: actions/checkout@v6 + with: + submodules: recursive + + - name: Build + run: | + conan install . --remote=bigfootpackages -pr:h=${{ matrix.conan_profile }} -pr:b=./ConanProfiles/Tools/clang --build=* -s:h build_type=${{ matrix.build_type }} + . ./build/${{ matrix.build_type }}/generators/conanbuild.sh + cmake -S . -B ./build/${{ matrix.build_type }} --toolchain ./build/${{ matrix.build_type }}/generators/conan_toolchain.cmake -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} -DCMAKE_UNITY_BUILD=${{ matrix.unity_build }} -G "Ninja" + cmake --build build/${{ matrix.build_type }} --parallel $(nproc) + . ./build/${{ matrix.build_type }}/generators/deactivate_conanbuild.sh + + - name: Unit Tests + run: | + cd ./build/${{ matrix.build_type }} + ctest . --output-on-failure + + clang-format: + runs-on: ubuntu-latest + timeout-minutes: 120 + container: + image: git.romainboullard.com/bigfootdev/linuxcppbuilder:main + name: "Clang Format Checks" + steps: + - name: Install Node.js + run: apt-get update && apt-get install -y nodejs + + - name: Checkout repository + uses: actions/checkout@v6 + with: + submodules: recursive + + - name: Clang Format Checks + run: chmod +x format.sh && ./format.sh --check Bin2CPP \ No newline at end of file diff --git a/.gitea/workflows/package.yml b/.gitea/workflows/package.yml new file mode 100644 index 0000000..36ea486 --- /dev/null +++ b/.gitea/workflows/package.yml @@ -0,0 +1,46 @@ +name: Conan Packaging + +on: + push: + branches: + - '**' + workflow_dispatch: + +jobs: + conan-packages: + runs-on: ubuntu-latest + timeout-minutes: 120 + container: + image: git.romainboullard.com/bigfootdev/linuxcppbuilder:main + env: + ARTIFACTORY_USER: ${{ secrets.ARTIFACTORY_USER }} + ARTIFACTORY_PASSWORD: ${{ secrets.ARTIFACTORY_PASSWORD }} + name: "Package Bin2CPP/1.0.0" + steps: + - name: Set BRANCH_NAME + run: echo "BRANCH_NAME=$(echo $GITHUB_REF_NAME | tr '[:upper:]' '[:lower:]')" >> $GITHUB_ENV + + - name: Install Node.js + run: apt-get update && apt-get install -y nodejs + + - name: Checkout repository + uses: actions/checkout@v6 + with: + submodules: recursive + + - name: Run Conan packaging + run: | + echo "Building Bin2CPP@$1.0.0" + CONAN_LOGIN_USERNAME=${ARTIFACTORY_USER} \ + CONAN_PASSWORD=${ARTIFACTORY_PASSWORD} \ + conan create . \ + --version=1.0.0 \ + --user=bigfootdev \ + --channel=${{ env.BRANCH_NAME }} \ + -pr:b=./ConanProfiles/Tools/clang -pr:h=./ConanProfiles/clang \ + --build=* --remote=bigfootpackages + + CONAN_LOGIN_USERNAME=${ARTIFACTORY_USER} \ + CONAN_PASSWORD=${ARTIFACTORY_PASSWORD} \ + conan upload bin2cpp/1.0.0@bigfootdev/${{ env.BRANCH_NAME }} \ + --only-recipe --remote=bigfootpackages diff --git a/.gitea/workflows/sonarqube.yml b/.gitea/workflows/sonarqube.yml new file mode 100644 index 0000000..b58d3b3 --- /dev/null +++ b/.gitea/workflows/sonarqube.yml @@ -0,0 +1,50 @@ +name: Bin2CPP + +on: + push: + branches: + - main + - Development + +jobs: + build-and-test: + runs-on: ubuntu-latest + timeout-minutes: 120 + container: + image: git.romainboullard.com/bigfootdev/linuxcppbuilder:main + name: "Sonarqube" + steps: + - name: Install Node.js + run: apt-get update && apt-get install -y nodejs + + - name: Checkout repository + uses: actions/checkout@v6 + with: + fetch-depth: 0 + submodules: recursive + + - name: Generate + run: | + conan profile detect + conan install . --remote=bigfootpackages -pr:h=./ConanProfiles/clang_coverage -pr:b=./ConanProfiles/Tools/clang --build=* -s:h build_type=Debug + . ./build/Debug/generators/conanbuild.sh + cmake -S . -B ./build/Debug --toolchain ./build/Debug/generators/conan_toolchain.cmake -DCMAKE_BUILD_TYPE=Debug -G "Ninja" + cmake --build build/Debug --parallel $(nproc) + . ./build/Debug/generators/deactivate_conanbuild.sh + + - name: Clang-Tidy + run: run-clang-tidy -p ./build/Debug/ >> tidy_result.txt + + - name: Infer + run: infer run --compilation-database build/Debug/compile_commands.json + + - name: SonarQube Scan + if: github.head_ref == 'main' || github.ref_name == 'main' + uses: SonarSource/sonarqube-scan-action@v7.0.0 + with: + args: > + -Dsonar.cxx.jsonCompilationDatabase=./build/Debug/compile_commands.json + -Dsonar.verbose=true + env: + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + SONAR_HOST_URL: ${{ secrets.SONAR_HOST }} \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..129a851 --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +[Bb]uild* +Environment/output +*.generated.h + +.scannerwork + +SonarqubeResult +*.profraw + +conan_imports_manifest.txt +CMakeUserPresets.json +conan.lock +conanbuildinfo.txt +conaninfo.txt +graph_info.json + +.idea + +test_package/Vendor + +graphviz \ No newline at end of file diff --git a/.inferconfig b/.inferconfig new file mode 100644 index 0000000..0e0dcd2 --- /dev/null +++ b/.inferconfig @@ -0,0 +1,3 @@ +{ + +} \ No newline at end of file diff --git a/Bin2CPP/Sources/Bin2CPP/CMakeLists.txt b/Bin2CPP/Sources/Bin2CPP/CMakeLists.txt new file mode 100644 index 0000000..6347ccb --- /dev/null +++ b/Bin2CPP/Sources/Bin2CPP/CMakeLists.txt @@ -0,0 +1,66 @@ +get_filename_component(PackageName ${CMAKE_CURRENT_SOURCE_DIR} NAME) +project(${PackageName}) + +add_executable(${PROJECT_NAME}) +target_compile_features(${PROJECT_NAME} PRIVATE cxx_std_20) + +file(GLOB_RECURSE BIN2CPPEXE_SOURCES + CONFIGURE_DEPENDS + ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp +) + +file(GLOB_RECURSE BIN2CPPEXE_HEADERS + CONFIGURE_DEPENDS + ${CMAKE_CURRENT_SOURCE_DIR}/*.h + ${CMAKE_CURRENT_SOURCE_DIR}/*.hpp +) + +target_sources(${PROJECT_NAME} + PRIVATE + ${BIN2CPPEXE_SOURCES} + PRIVATE + FILE_SET HEADERS + BASE_DIRS + ${CMAKE_CURRENT_SOURCE_DIR}/Include + FILES + ${BIN2CPPEXE_HEADERS} +) + +target_link_libraries(${PROJECT_NAME} + PRIVATE + Bin2CPPCompileAndLinkFlags + Bin2CPPLib + CLI11::CLI11 + ${CMAKE_DL_LIBS}) + +source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} PREFIX Src FILES ${BIN2CPPEXE_SOURCES} ${BIN2CPPEXE_HEADERS}) + +set_target_properties(${PROJECT_NAME} PROPERTIES FOLDER Bin2CPP/${ParentFolder}) + +##################ASAN SETUP################### + +if(${ASAN}) + if(MSVC) + get_filename_component(MSVC_BIN_DIR "${CMAKE_CXX_COMPILER}" DIRECTORY) + set(ASAN_DLL "${MSVC_BIN_DIR}/clang_rt.asan_dynamic-x86_64.dll") + + add_custom_command( + OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}-asan.timestamp" + DEPENDS "${ASAN_DLL}" + COMMAND ${CMAKE_COMMAND} -E copy_if_different "${ASAN_DLL}" "$" + COMMAND ${CMAKE_COMMAND} -E touch "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}-asan.timestamp" + COMMENT "Copying ASan DLL for ${PROJECT_NAME}" + ) + + add_custom_target(${PROJECT_NAME}Asan + DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}-asan.timestamp" + ) + + add_dependencies(${PROJECT_NAME} ${PROJECT_NAME}Asan) + set_target_properties(${PROJECT_NAME}Asan PROPERTIES FOLDER UtilityTargets/Bin2CPP/${ParentFolder}) + endif() +endif() + +########INSTALL + +install(TARGETS ${PROJECT_NAME} DESTINATION bin) \ No newline at end of file diff --git a/Bin2CPP/Sources/Bin2CPP/main.cpp b/Bin2CPP/Sources/Bin2CPP/main.cpp new file mode 100644 index 0000000..d948838 --- /dev/null +++ b/Bin2CPP/Sources/Bin2CPP/main.cpp @@ -0,0 +1,68 @@ +#include +#include + +#include + +int main(int argc, char** argv) +{ + CLI::App app {"Bin2CPP allows you to generate a C++ header containing the binary content of an input file"}; + argv = app.ensure_utf8(argv); + + std::string input; + app.add_option("-f,--input", input, "The input file")->required(); + std::string output; + app.add_option("-o,--output", output, "The output file")->required(); + std::optional arrayType; + app.add_option("--arrayType", arrayType, "The type of the array"); + std::optional arrayInclude; + app.add_option("--arrayInclude", arrayInclude, "Include for the array type"); + std::optional arrayName; + app.add_option("--arrayName", arrayName, "The array name"); + std::optional customNamespace; + app.add_option("--namespace", customNamespace, "The namespace"); + bool verbose = false; + app.add_option("--verbose", verbose, "Activate logging"); + + CLI11_PARSE(app, argc, argv); + + std::unique_ptr::Lifetime> loggerLifetime = + verbose ? std::make_unique::Lifetime>() : nullptr; + + Bin2CPP::Generator generator(input); + if (arrayType) + { + generator.SetMapping(Bin2CPP::MappingKey::ARRAY_TYPE, arrayType.value()); + } + if (arrayInclude) + { + generator.SetMapping(Bin2CPP::MappingKey::ARRAY_TYPE_INCLUDE, arrayInclude.value()); + } + if (arrayName) + { + generator.SetMapping(Bin2CPP::MappingKey::ARRAY_NAME, arrayName.value()); + } + if (customNamespace) + { + generator.SetMapping(Bin2CPP::MappingKey::NAMESPACE, customNamespace.value()); + } + + if (!generator.Generate()) + { + BIN2CPP_LOG_ERROR("Failed to generate '{}'", input); + return 1; + } + + std::ofstream out(output); + if (!out) + { + BIN2CPP_LOG_ERROR("Failed to open '{}'", output); + return 1; + } + + out << generator.Get(); + out.close(); + + BIN2CPP_LOG_INFO("'{}' Generated !", output); + + return 0; +} diff --git a/Bin2CPP/Sources/Bin2CPPLib/CMakeLists.txt b/Bin2CPP/Sources/Bin2CPPLib/CMakeLists.txt new file mode 100644 index 0000000..fcfad89 --- /dev/null +++ b/Bin2CPP/Sources/Bin2CPPLib/CMakeLists.txt @@ -0,0 +1,49 @@ +get_filename_component(PackageName ${CMAKE_CURRENT_SOURCE_DIR} NAME) +project(${PackageName}) + +add_library(${PROJECT_NAME} STATIC) +target_compile_features(${PROJECT_NAME} PRIVATE cxx_std_20) + +file(GLOB_RECURSE BIN2CPPLIB_SOURCES + CONFIGURE_DEPENDS + ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp +) + +file(GLOB_RECURSE BIN2CPPLIB_HEADERS + CONFIGURE_DEPENDS + ${CMAKE_CURRENT_SOURCE_DIR}/*.h + ${CMAKE_CURRENT_SOURCE_DIR}/*.hpp +) + +file(GLOB_RECURSE BIN2CPPLIB_OTHERS + CONFIGURE_DEPENDS + ${CMAKE_CURRENT_SOURCE_DIR}/*.hpp.in +) + +target_sources(${PROJECT_NAME} + PRIVATE + ${BIN2CPPLIB_SOURCES} + PUBLIC + FILE_SET HEADERS + BASE_DIRS + ${CMAKE_CURRENT_SOURCE_DIR}/Include + FILES + ${BIN2CPPLIB_HEADERS} +) + +target_link_libraries(${PROJECT_NAME} + PRIVATE + Bin2CPPCompileAndLinkFlags + PUBLIC + quill::quill + magic_enum::magic_enum) + +target_compile_definitions(${PROJECT_NAME} + PUBLIC + QUILL_DISABLE_NON_PREFIXED_MACROS) + +set_source_files_properties(${CMAKE_CURRENT_SOURCE_DIR}/MimallocImpl.cpp PROPERTIES SKIP_UNITY_BUILD_INCLUSION ON) + +source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} PREFIX Src FILES ${BIN2CPPLIB_SOURCES} ${BIN2CPPLIB_HEADERS} ${BIN2CPPLIB_OTHERS}) + +set_target_properties(${PROJECT_NAME} PROPERTIES FOLDER Bin2CPP/${ParentFolder}) \ No newline at end of file diff --git a/Bin2CPP/Sources/Bin2CPPLib/Generator.cpp b/Bin2CPP/Sources/Bin2CPPLib/Generator.cpp new file mode 100644 index 0000000..77ce323 --- /dev/null +++ b/Bin2CPP/Sources/Bin2CPPLib/Generator.cpp @@ -0,0 +1,280 @@ +/********************************************************************* + * \file Generator.cpp + * + * \author Romain BOULLARD + * \date February 2026 + *********************************************************************/ +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace Bin2CPP +{ +Generator::Generator(const std::string_view p_inputFile): + m_inputFile(p_inputFile) +{ +} + +/****************************************************************************************/ + +void Generator::SetMapping(const MappingKey p_key, const std::string_view p_value) +{ + const std::optional index = magic_enum::enum_index(p_key); + BIN2CPP_CRITICAL_ASSERT(index.has_value(), "Incorrect mapping key! Got {}", magic_enum::enum_integer(p_key)); + + m_mappingTable[index.value()] = p_value; +} + +/****************************************************************************************/ + +bool Generator::Generate() +{ + if (!ComputeMappings()) + { + BIN2CPP_LOG_ERROR("Invalid mappings! Can't generate file!"); + return false; + } + + m_result = g_generatedTemplate; + + for (std::uint32_t i = 0; i < m_mappingTable.size(); ++i) + { + const std::string KEY = magic_enum::enum_name(magic_enum::enum_value(i)).data(); + const std::string& VALUE = m_mappingTable[i]; + + const std::string pattern = "\\{\\{" + KEY + "\\}\\}"; + std::regex placeholderRegex(pattern); + m_result = std::regex_replace(m_result, placeholderRegex, VALUE); + } + + return Validate(); +} + +/****************************************************************************************/ + +std::string_view Generator::Get() const +{ + return m_result; +} + +/****************************************************************************************/ + +bool Generator::ComputeMappings() +{ + std::filesystem::path file {m_inputFile.data()}; + if (!std::filesystem::exists(file)) + { + BIN2CPP_LOG_ERROR("File '{}' does not exists!", m_inputFile); + return false; + } + if (std::filesystem::is_directory(file)) + { + BIN2CPP_LOG_ERROR("'{}' is not a file!", m_inputFile); + return false; + } + + if (!ComputeFilename()) + { + BIN2CPP_LOG_ERROR("Failed to compute filename"); + return false; + } + if (!ComputeGuardName()) + { + BIN2CPP_LOG_ERROR("Failed to compute GuardName"); + return false; + } + if (!ComputeArrayTypeInclude()) + { + BIN2CPP_LOG_ERROR("Failed to compute ArrayTypeInclude"); + return false; + } + if (!ComputeArrayType()) + { + BIN2CPP_LOG_ERROR("Failed to compute ArrayType"); + return false; + } + if (!ComputeArrayName()) + { + BIN2CPP_LOG_ERROR("Failed to compute ArrayName"); + return false; + } + + std::ifstream in {m_inputFile.data(), std::ios::binary}; + if (!in) + { + BIN2CPP_LOG_ERROR("Failed to open '{}'", m_inputFile); + return false; + } + in.seekg(0, std::ios::end); + const size_t fileSize = static_cast(in.tellg()); + in.seekg(0, std::ios::beg); + + std::vector data(fileSize); + in.read(std::bit_cast(data.data()), fileSize); + + if (!ComputeArraySize(data)) + { + BIN2CPP_LOG_ERROR("Failed to compute ArraySize"); + return false; + } + if (!ComputeData(data)) + { + BIN2CPP_LOG_ERROR("Failed to compute Data"); + return false; + } + + return true; +} + +/****************************************************************************************/ + +bool Generator::ComputeFilename() +{ + std::filesystem::path file {m_inputFile.data()}; + m_mappingTable[magic_enum::enum_index(MappingKey::FILENAME).value()] = file.filename().string(); + + return true; +} + +/****************************************************************************************/ + +bool Generator::ComputeGuardName() +{ + std::filesystem::path file {m_inputFile.data()}; + std::string guardName = file.filename().string(); + + std::transform(guardName.begin(), + guardName.end(), + guardName.begin(), + [](const char c) + { + if (!std::isalnum(c)) + { + return static_cast('_'); + } + else + { + return std::toupper(c); + } + }); + m_mappingTable[magic_enum::enum_index(MappingKey::GUARD_NAME).value()] = std::format("{}_HPP", guardName); + + return true; +} + +/****************************************************************************************/ + +bool Generator::ComputeArrayTypeInclude() +{ + std::string& value = m_mappingTable[magic_enum::enum_index(MappingKey::ARRAY_TYPE_INCLUDE).value()]; + if (value.empty()) + { + value = ""; + } + + return true; +} + +/****************************************************************************************/ + +bool Generator::ComputeArrayType() +{ + std::string& value = m_mappingTable[magic_enum::enum_index(MappingKey::ARRAY_TYPE).value()]; + if (value.empty()) + { + value = "std::array"; + } + + return true; +} + +/****************************************************************************************/ + +bool Generator::ComputeArrayName() +{ + std::string& value = m_mappingTable[magic_enum::enum_index(MappingKey::ARRAY_NAME).value()]; + if (value.empty()) + { + std::filesystem::path file {m_inputFile.data()}; + value = std::format("g_{}", file.filename().string()); + + std::transform(value.begin(), + value.end(), + value.begin(), + [](const char c) + { + if (!std::isalnum(c)) + { + return static_cast('_'); + } + else + { + return static_cast(c); + } + }); + } + + return true; +} + +/****************************************************************************************/ + +bool Generator::ComputeArraySize(const std::span p_data) +{ + m_mappingTable[magic_enum::enum_index(MappingKey::ARRAY_SIZE).value()] = std::to_string(p_data.size()); + return true; +} + +/****************************************************************************************/ + +bool Generator::ComputeData(const std::span p_data) +{ + std::string& value = m_mappingTable[magic_enum::enum_index(MappingKey::DATA).value()]; + value.clear(); + + constexpr std::size_t bytesPerLine = 16; + constexpr std::string_view linePrefix = "\n "; + + for (std::size_t i = 0; i < p_data.size(); ++i) + { + if (i > 0 && i % bytesPerLine == 0) + { + value.append(linePrefix.data(), linePrefix.size()); + } + + const std::string element = std::format("std::byte{{0x{:02X}}}", std::to_integer(p_data[i])); + value.append(element.data(), element.size()); + + if (i + 1 < p_data.size()) + { + value.append(", "); + } + } + + return true; +} + +/****************************************************************************************/ + +bool Generator::Validate() +{ + std::regex unreplacedRegex(R"(\{\{[A-Z_]+\}\})"); + std::smatch match; + + if (std::regex_search(m_result, match, unreplacedRegex)) + { + BIN2CPP_LOG_ERROR("Warning: Unreplaced placeholder found: {}", match.str()); + return false; + } + + return true; +} +} // namespace Bin2CPP diff --git a/Bin2CPP/Sources/Bin2CPPLib/Include/Assert.hpp b/Bin2CPP/Sources/Bin2CPPLib/Include/Assert.hpp new file mode 100644 index 0000000..e740fd4 --- /dev/null +++ b/Bin2CPP/Sources/Bin2CPPLib/Include/Assert.hpp @@ -0,0 +1,121 @@ +/********************************************************************* + * \file Assert.hpp + * + * \author Romain BOULLARD + * \date October 2025 + *********************************************************************/ +#ifndef BIN2CPP_ASSERT_HPP +#define BIN2CPP_ASSERT_HPP +#include + +#if defined BIN2CPP_NOT_OPTIMIZED + +#include +#include + +#if defined BIN2CPP_LINUX +#include +#endif + +namespace Bin2CPP +{ +class AssertHandler +{ + public: + AssertHandler() = delete; + + AssertHandler(const AssertHandler& p_handler) = delete; + AssertHandler(AssertHandler&& p_handler) = delete; + + ~AssertHandler() = delete; + + /** + * Handle an assertion. + * + * \param p_location Location of the assertion. + * \param p_format Format string for the assertion message. + * \param p_args Arguments for the format string. + */ + template + static void Handle(const std::source_location& p_location, std::format_string p_format, ARGS&&... p_args) + { + BIN2CPP_LOG_FATAL("Assert: {} (File:{}, Line:{}, Function:{})", + std::format(p_format, std::forward(p_args)...), + p_location.file_name(), + p_location.line(), + p_location.function_name()); + } + + AssertHandler& operator=(const AssertHandler& p_handler) = delete; + AssertHandler& operator=(AssertHandler&& p_handler) = delete; +}; +} // namespace Bin2CPP + +#if defined BIN2CPP_WINDOWS +#define BIN2CPP_BREAK \ + do \ + { \ + __debugbreak(); \ + } while (false) +#elif defined BIN2CPP_LINUX +#define BIN2CPP_BREAK \ + do \ + { \ + std::raise(SIGTRAP); \ + } while (false) +#endif + +#define BIN2CPP_ASSERT(p_assert, p_message, ...) \ + do \ + { \ + constexpr std::source_location location = std::source_location::current(); \ + if (!(p_assert)) [[unlikely]] \ + { \ + Bin2CPP::AssertHandler::Handle(location, p_message __VA_OPT__(, ) __VA_ARGS__); \ + if (Bin2CPP::Singleton::HasInstance()) \ + { \ + Bin2CPP::Singleton::Instance().Flush(); \ + } \ + BIN2CPP_BREAK; \ + } \ + } while (false) + +#define BIN2CPP_SOFT_ASSERT(p_assert, p_message, ...) \ + do \ + { \ + constexpr std::source_location location = std::source_location::current(); \ + if (!(p_assert)) [[unlikely]] \ + { \ + Bin2CPP::AssertHandler::Handle(location, p_message __VA_OPT__(, ) __VA_ARGS__); \ + if (Bin2CPP::Singleton::HasInstance()) \ + { \ + Bin2CPP::Singleton::Instance().Flush(); \ + } \ + BIN2CPP_BREAK; \ + } \ + } while (false) + +#define BIN2CPP_CRITICAL_ASSERT(p_assert, p_message, ...) \ + do \ + { \ + constexpr std::source_location location = std::source_location::current(); \ + if (!(p_assert)) [[unlikely]] \ + { \ + Bin2CPP::AssertHandler::Handle(location, p_message __VA_OPT__(, ) __VA_ARGS__); \ + if (Bin2CPP::Singleton::HasInstance()) \ + { \ + Bin2CPP::Singleton::Instance().Flush(); \ + } \ + BIN2CPP_BREAK; \ + std::abort(); \ + } \ + } while (false) + +#else +#define BIN2CPP_ASSERT(p_assert, p_message, ...) + +#define BIN2CPP_SOFT_ASSERT(p_assert, p_message, ...) + +#define BIN2CPP_CRITICAL_ASSERT(p_assert, p_message, ...) +#endif +#endif diff --git a/Bin2CPP/Sources/Bin2CPPLib/Include/GeneratedTemplate.hpp b/Bin2CPP/Sources/Bin2CPPLib/Include/GeneratedTemplate.hpp new file mode 100644 index 0000000..2645f23 --- /dev/null +++ b/Bin2CPP/Sources/Bin2CPPLib/Include/GeneratedTemplate.hpp @@ -0,0 +1,36 @@ +/********************************************************************* + * \file GeneratedTemplate.hpp + * + * \author Romain BOULLARD + * \date March 2026 + *********************************************************************/ +#ifndef BIN2CPP_GENERATED_TEMPLATE_HPP +#define BIN2CPP_GENERATED_TEMPLATE_HPP +#include + +namespace Bin2CPP +{ +constexpr std::string_view g_generatedTemplate = R"(/** + * Auto-generated header from: {{FILENAME}} + * Generated by Bin2CPP + * + * DO NOT TOUCH + */ +#ifndef {{GUARD_NAME}} +#define {{GUARD_NAME}} +#include {{ARRAY_TYPE_INCLUDE}} +#include + +namespace {{NAMESPACE}} +{ + inline constexpr {{ARRAY_TYPE}} {{ARRAY_NAME}} = { + {{DATA}} + }; + +} // namespace {{NAMESPACE}} + +#endif // {{GUARD_NAME}} +)"; +} // namespace Bin2CPP + +#endif diff --git a/Bin2CPP/Sources/Bin2CPPLib/Include/Generator.hpp b/Bin2CPP/Sources/Bin2CPPLib/Include/Generator.hpp new file mode 100644 index 0000000..a18b646 --- /dev/null +++ b/Bin2CPP/Sources/Bin2CPPLib/Include/Generator.hpp @@ -0,0 +1,157 @@ +/********************************************************************* + * \file Generator.hpp + * + * \author Romain BOULLARD + * \date February 2026 + *********************************************************************/ +#ifndef BIN2CPP_GENERATOR_HPP +#define BIN2CPP_GENERATOR_HPP +#include + +#include +#include +#include + +namespace Bin2CPP +{ +enum class MappingKey +{ + FILENAME, + GUARD_NAME, + NAMESPACE, + ARRAY_TYPE_INCLUDE, + ARRAY_TYPE, + ARRAY_SIZE, + ARRAY_NAME, + DATA +}; + +class Generator +{ + public: + /** + * Constructor + * + * \param p_inputFile the Input file + */ + Generator(const std::string_view p_inputFile); + + Generator(const Generator& p_generator) = default; + Generator(Generator&& p_generator) = default; + + /** + * Set a mapping + * + */ + void SetMapping(const MappingKey p_key, const std::string_view p_value); + + /** + * Generate + * + * \return True if successful, false otherwise + */ + [[nodiscard]] + bool Generate(); + + /** + * Get + * + */ + [[nodiscard]] + std::string_view Get() const; + + ~Generator() = default; + + Generator& operator=(const Generator& p_generator) = default; + Generator& operator=(Generator&& p_generator) = default; + + private: + /** + * Compute the mappings + * + * \return True if all mappings have valid values, false otherwise + */ + [[nodiscard]] + bool ComputeMappings(); + + /** + * Compute the filename mapping + * + * \return True if success, false otherwise + */ + [[nodiscard]] + bool ComputeFilename(); + + /** + * Compute the guardName mapping + * + * \return True if success, false otherwise + */ + [[nodiscard]] + bool ComputeGuardName(); + + /** + * Compute the arrayTypeInclude mapping + * + * \return True if success, false otherwise + */ + [[nodiscard]] + bool ComputeArrayTypeInclude(); + + /** + * Compute the arrayType mapping + * + * \return True if success, false otherwise + */ + [[nodiscard]] + bool ComputeArrayType(); + + /** + * Compute the arrayName mapping + * + * \return True if success, false otherwise + */ + [[nodiscard]] + bool ComputeArrayName(); + + /** + * Compute the arraySize mapping + * + * \return True if success, false otherwise + */ + [[nodiscard]] + bool ComputeArraySize(const std::span p_data); + + /** + * Compute the data mapping + * + * \return True if success, false otherwise + */ + [[nodiscard]] + bool ComputeData(const std::span p_data); + + /** + * Validate generated is correct (no remaining tags) + * + * \return True if valid, false otherwise + */ + [[nodiscard]] + bool Validate(); + + /** + * The mapping table + */ + std::array()> m_mappingTable; + + /** + * The input file + */ + std::string m_inputFile; + + /** + * The result + */ + std::string m_result; +}; +} // namespace Bin2CPP +#endif diff --git a/Bin2CPP/Sources/Bin2CPPLib/Include/Log.hpp b/Bin2CPP/Sources/Bin2CPPLib/Include/Log.hpp new file mode 100644 index 0000000..956b409 --- /dev/null +++ b/Bin2CPP/Sources/Bin2CPPLib/Include/Log.hpp @@ -0,0 +1,133 @@ +/********************************************************************* + * \file Log.hpp + * + * \author Romain BOULLARD + * \date February 2026 + *********************************************************************/ +#ifndef BIN2CPP_LOG_HPP +#define BIN2CPP_LOG_HPP +#include + +#include + +#ifdef BIN2CPP_WINDOWS +#pragma warning(disable: 4702) +#endif +#include +#include +#include +#include +#include +#if defined BIN2CPP_WINDOWS +#pragma warning(default: 4702) +#endif + +namespace Bin2CPP +{ +class Log +{ + public: + Log(); + + Log(const Log& p_logger) = delete; + Log(Log&& p_logger) = delete; + + /** + * Register a logger. + * + * \return The logger, nullptr if it does not exist + */ + [[nodiscard]] + quill::Logger* GetLogger(); + + /* + * Flush all the loggers + * + */ + void Flush(); + + ~Log(); + + Log& operator=(const Log& p_logger) = delete; + Log& operator=(Log&& p_logger) = delete; + + private: + /* + * The sinks + */ + std::array, 1> m_sinks; +}; +} // namespace Bin2CPP + +#define BIN2CPP_LOG_DEBUG(fmt, ...) \ + do \ + { \ + if (Bin2CPP::Singleton::HasInstance()) \ + { \ + if (quill::Logger* logger = Bin2CPP::Singleton::Instance().GetLogger()) \ + { \ + QUILL_LOG_DEBUG(logger, fmt __VA_OPT__(, ) __VA_ARGS__); \ + } \ + } \ + } while (0) + +#define BIN2CPP_LOG_TRACE(fmt, ...) \ + do \ + { \ + if (Bin2CPP::Singleton::HasInstance()) \ + { \ + if (quill::Logger* logger = Bin2CPP::Singleton::Instance().GetLogger()) \ + { \ + QUILL_LOG_TRACE_L3(logger, fmt __VA_OPT__(, ) __VA_ARGS__); \ + } \ + } \ + } while (0) + +#define BIN2CPP_LOG_INFO(fmt, ...) \ + do \ + { \ + if (Bin2CPP::Singleton::HasInstance()) \ + { \ + if (quill::Logger* logger = Bin2CPP::Singleton::Instance().GetLogger()) \ + { \ + QUILL_LOG_INFO(logger, fmt __VA_OPT__(, ) __VA_ARGS__); \ + } \ + } \ + } while (0) + +#define BIN2CPP_LOG_WARN(fmt, ...) \ + do \ + { \ + if (Bin2CPP::Singleton::HasInstance()) \ + { \ + if (quill::Logger* logger = Bin2CPP::Singleton::Instance().GetLogger()) \ + { \ + QUILL_LOG_WARNING(logger, fmt __VA_OPT__(, ) __VA_ARGS__); \ + } \ + } \ + } while (0) + +#define BIN2CPP_LOG_ERROR(fmt, ...) \ + do \ + { \ + if (Bin2CPP::Singleton::HasInstance()) \ + { \ + if (quill::Logger* logger = Bin2CPP::Singleton::Instance().GetLogger()) \ + { \ + QUILL_LOG_ERROR(logger, fmt __VA_OPT__(, ) __VA_ARGS__); \ + } \ + } \ + } while (0) + +#define BIN2CPP_LOG_FATAL(fmt, ...) \ + do \ + { \ + if (Bin2CPP::Singleton::HasInstance()) \ + { \ + if (quill::Logger* logger = Bin2CPP::Singleton::Instance().GetLogger()) \ + { \ + QUILL_LOG_CRITICAL(logger, fmt __VA_OPT__(, ) __VA_ARGS__); \ + } \ + } \ + } while (0) +#endif diff --git a/Bin2CPP/Sources/Bin2CPPLib/Include/Singleton.hpp b/Bin2CPP/Sources/Bin2CPPLib/Include/Singleton.hpp new file mode 100644 index 0000000..6eb4780 --- /dev/null +++ b/Bin2CPP/Sources/Bin2CPPLib/Include/Singleton.hpp @@ -0,0 +1,104 @@ +/********************************************************************* + * \file Singleton.hpp + * + * \author Romain BOULLARD + * \date February 2026 + *********************************************************************/ +#ifndef BIN2CPP_SINGLETON_HPP +#define BIN2CPP_SINGLETON_HPP +#include + +namespace Bin2CPP +{ +template +class Singleton +{ + public: + Singleton() = delete; + + Singleton(const Singleton& p_singleton) = delete; + Singleton(Singleton&& p_singleton) = delete; + + ~Singleton() = delete; + + /** + * Get the instance. + * + * \return The instance + */ + static TYPE& Instance() + { + return ms_instance.value(); + } + + /** + * Is the instance initialized + * + * \return True if initialized, false otherwise + */ + static bool HasInstance() + { + return ms_instance.has_value(); + } + + class Lifetime + { + public: + /** + * Constructor. + * + * \param p_args Arguments for the singleton + */ + template + explicit Lifetime(ARGS&&... p_args) + { + Initialize(std::forward(p_args)...); + } + + Lifetime(const Lifetime& p_lifetime) = delete; + Lifetime(Lifetime&& p_lifetime) = delete; + + ~Lifetime() + { + Finalize(); + } + + [[nodiscard]] + Lifetime& operator=(const Lifetime& p_lifetime) = delete; + [[nodiscard]] + Lifetime& operator=(Lifetime&& p_lifetime) = delete; + }; + + [[nodiscard]] + Singleton& operator=(const Singleton& p_singleton) = delete; + [[nodiscard]] + Singleton& operator=(Singleton&& p_singleton) = delete; + + private: + /** + * Initialize the singleton. + * + * \param p_args Arguments for the singleton + */ + template + static void Initialize(ARGS&&... p_args) + { + ms_instance.emplace(std::forward(p_args)...); + } + + /** + * Finalize the singleton. + * + */ + static void Finalize() + { + ms_instance.reset(); + } + + /** + * The singleton. + */ + inline static std::optional ms_instance; +}; +} // namespace Bin2CPP +#endif diff --git a/Bin2CPP/Sources/Bin2CPPLib/Log.cpp b/Bin2CPP/Sources/Bin2CPPLib/Log.cpp new file mode 100644 index 0000000..fa02a3b --- /dev/null +++ b/Bin2CPP/Sources/Bin2CPPLib/Log.cpp @@ -0,0 +1,51 @@ +/******************************************************************* + * \file Log.cpp + * + * \author Romain BOULLARD + * \date February 2026 + *********************************************************************/ +#include + +namespace Bin2CPP +{ +Log::Log() +{ + quill::Backend::start(); + + m_sinks[0] = quill::Frontend::create_or_get_sink("ConsoleSink"); + + quill::Logger* logger = quill::Frontend::create_or_get_logger("Bin2CPP", m_sinks[0]); + logger->set_log_level(quill::LogLevel::TraceL3); +} + +/****************************************************************************************/ + +quill::Logger* Log::GetLogger() +{ + return quill::Frontend::get_logger("Bin2CPP"); +} + +/****************************************************************************************/ + +void Log::Flush() +{ + for (quill::Logger* logger: quill::Frontend::get_all_loggers()) + { + logger->flush_log(); + } +} + +/****************************************************************************************/ + +Log::~Log() +{ + Flush(); + + for (quill::Logger* logger: quill::Frontend::get_all_loggers()) + { + quill::Frontend::remove_logger(logger); + } + + quill::Backend::stop(); +} +} // namespace Bin2CPP diff --git a/Bin2CPP/Sources/CMakeLists.txt b/Bin2CPP/Sources/CMakeLists.txt new file mode 100644 index 0000000..96c48ae --- /dev/null +++ b/Bin2CPP/Sources/CMakeLists.txt @@ -0,0 +1,2 @@ +add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/Bin2CPPLib) +add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/Bin2CPP) \ No newline at end of file diff --git a/Bin2CPP/Tests/Bin2CPPLib/CMakeLists.txt b/Bin2CPP/Tests/Bin2CPPLib/CMakeLists.txt new file mode 100644 index 0000000..48e69ba --- /dev/null +++ b/Bin2CPP/Tests/Bin2CPPLib/CMakeLists.txt @@ -0,0 +1,90 @@ +get_filename_component(PackageName ${CMAKE_CURRENT_SOURCE_DIR} NAME) +project(${PackageName}Tests) + +add_executable(${PROJECT_NAME}) +target_compile_features(${PROJECT_NAME} PRIVATE cxx_std_20) + +file(GLOB_RECURSE BIN2CPPLIBTESTS_SOURCES + CONFIGURE_DEPENDS + ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp +) + +file(GLOB_RECURSE BIN2CPPLIBTESTS_HEADERS + CONFIGURE_DEPENDS + ${CMAKE_CURRENT_SOURCE_DIR}/*.h + ${CMAKE_CURRENT_SOURCE_DIR}/*.hpp +) + +target_sources(${PROJECT_NAME} + PRIVATE + ${BIN2CPPLIBTESTS_SOURCES} + PRIVATE + FILE_SET HEADERS + BASE_DIRS + ${CMAKE_CURRENT_SOURCE_DIR}/Include + FILES + ${BIN2CPPLIBTESTS_HEADERS} +) + +target_link_libraries(${PROJECT_NAME} + PRIVATE + Bin2CPPCompileAndLinkFlags + Bin2CPPLib + gtest::gtest) + +include(GoogleTest) +gtest_discover_tests(${PROJECT_NAME} XML_OUTPUT_DIR ${CMAKE_BINARY_DIR}/TestResults/) + +source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} PREFIX Src/ FILES ${BIN2CPPLIBTESTS_SOURCES} ${BIN2CPPLIBTESTS_HEADERS}) + +set_target_properties(${PROJECT_NAME} PROPERTIES FOLDER Tests/Bin2CPP/${ParentFolder}) + +##################ASAN SETUP################### + +if(${ASAN}) + if(MSVC) + get_filename_component(MSVC_BIN_DIR "${CMAKE_CXX_COMPILER}" DIRECTORY) + set(ASAN_DLL "${MSVC_BIN_DIR}/clang_rt.asan_dynamic-x86_64.dll") + + add_custom_command( + OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}-asan.timestamp" + DEPENDS "${ASAN_DLL}" + COMMAND ${CMAKE_COMMAND} -E copy_if_different "${ASAN_DLL}" "$" + COMMAND ${CMAKE_COMMAND} -E touch "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}-asan.timestamp" + COMMENT "Copying ASan DLL for ${PROJECT_NAME}" + ) + + add_custom_target(${PROJECT_NAME}Asan + DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}-asan.timestamp" + ) + + add_dependencies(${PROJECT_NAME} ${PROJECT_NAME}Asan) + set_target_properties(${PROJECT_NAME}Asan PROPERTIES FOLDER UtilityTargets/Tests/Bin2CPP) + endif() +endif() + +##################COPY FIXTURE FOLDER################### +if(NOT EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/Fixture) + file(MAKE_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/Fixture) +endif() + +file(GLOB_RECURSE FIXTURE_FILES + CONFIGURE_DEPENDS + ${CMAKE_CURRENT_SOURCE_DIR}/Fixture/* +) + +add_custom_command( + OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}-fixture.timestamp" + DEPENDS ${FIXTURE_FILES} + COMMAND ${CMAKE_COMMAND} -E remove_directory $/Fixture + COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_SOURCE_DIR}/Fixture $/Fixture + COMMAND ${CMAKE_COMMAND} -E touch "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}-fixture.timestamp" + COMMENT "Copying Fixture folder for ${PROJECT_NAME}" +) + +add_custom_target(${PROJECT_NAME}Fixture + DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}-fixture.timestamp" +) + +add_dependencies(${PROJECT_NAME} ${PROJECT_NAME}Fixture) +set_target_properties(${PROJECT_NAME}Fixture PROPERTIES FOLDER UtilityTargets/Tests/Bin2CPP) \ No newline at end of file diff --git a/Bin2CPP/Tests/Bin2CPPLib/Fixture/toto.txt b/Bin2CPP/Tests/Bin2CPPLib/Fixture/toto.txt new file mode 100644 index 0000000..5e1c309 --- /dev/null +++ b/Bin2CPP/Tests/Bin2CPPLib/Fixture/toto.txt @@ -0,0 +1 @@ +Hello World \ No newline at end of file diff --git a/Bin2CPP/Tests/Bin2CPPLib/Generator.cpp b/Bin2CPP/Tests/Bin2CPPLib/Generator.cpp new file mode 100644 index 0000000..cb89089 --- /dev/null +++ b/Bin2CPP/Tests/Bin2CPPLib/Generator.cpp @@ -0,0 +1,133 @@ +/********************************************************************* + * \file Generator.cpp + * + * \author Romain BOULLARD + * \date February 2026 + *********************************************************************/ +#include + +#include + +#include + +namespace Bin2CPP +{ +class GeneratorFixture: public ::testing::Test +{ + protected: + Singleton::Lifetime m_lifetime; +}; + +/****************************************************************************************/ + +TEST_F(GeneratorFixture, GenerateDefault) +{ + Generator generator("Fixture/toto.txt"); + EXPECT_TRUE(generator.Generate()); + + constexpr std::string_view expectedResult = R"(/** + * Auto-generated header from: toto.txt + * Generated by Bin2CPP + * + * DO NOT TOUCH + */ +#ifndef TOTO_TXT_HPP +#define TOTO_TXT_HPP +#include +#include + +namespace +{ + inline constexpr std::array g_toto_txt = { + std::byte{0x48}, std::byte{0x65}, std::byte{0x6C}, std::byte{0x6C}, std::byte{0x6F}, std::byte{0x20}, std::byte{0x57}, std::byte{0x6F}, std::byte{0x72}, std::byte{0x6C}, std::byte{0x64} + }; + +} // namespace + +#endif // TOTO_TXT_HPP +)"; + EXPECT_STREQ(expectedResult.data(), generator.Get().data()); +} + +/****************************************************************************************/ + +TEST_F(GeneratorFixture, GenerateNamespace) +{ + Generator generator("Fixture/toto.txt"); + generator.SetMapping(MappingKey::NAMESPACE, "Test"); + EXPECT_TRUE(generator.Generate()); + + constexpr std::string_view expectedResult = R"(/** + * Auto-generated header from: toto.txt + * Generated by Bin2CPP + * + * DO NOT TOUCH + */ +#ifndef TOTO_TXT_HPP +#define TOTO_TXT_HPP +#include +#include + +namespace Test +{ + inline constexpr std::array g_toto_txt = { + std::byte{0x48}, std::byte{0x65}, std::byte{0x6C}, std::byte{0x6C}, std::byte{0x6F}, std::byte{0x20}, std::byte{0x57}, std::byte{0x6F}, std::byte{0x72}, std::byte{0x6C}, std::byte{0x64} + }; + +} // namespace Test + +#endif // TOTO_TXT_HPP +)"; + EXPECT_STREQ(expectedResult.data(), generator.Get().data()); +} + +/****************************************************************************************/ + +TEST_F(GeneratorFixture, GenerateCustomArray) +{ + Generator generator("Fixture/toto.txt"); + generator.SetMapping(MappingKey::ARRAY_TYPE, "eastl::array"); + generator.SetMapping(MappingKey::ARRAY_TYPE_INCLUDE, ""); + generator.SetMapping(MappingKey::ARRAY_NAME, "g_myArray"); + EXPECT_TRUE(generator.Generate()); + + constexpr std::string_view expectedResult = R"(/** + * Auto-generated header from: toto.txt + * Generated by Bin2CPP + * + * DO NOT TOUCH + */ +#ifndef TOTO_TXT_HPP +#define TOTO_TXT_HPP +#include +#include + +namespace +{ + inline constexpr eastl::array g_myArray = { + std::byte{0x48}, std::byte{0x65}, std::byte{0x6C}, std::byte{0x6C}, std::byte{0x6F}, std::byte{0x20}, std::byte{0x57}, std::byte{0x6F}, std::byte{0x72}, std::byte{0x6C}, std::byte{0x64} + }; + +} // namespace + +#endif // TOTO_TXT_HPP +)"; + EXPECT_STREQ(expectedResult.data(), generator.Get().data()); +} + +/****************************************************************************************/ + +TEST_F(GeneratorFixture, NonExistentFile) +{ + Generator generator("Fixture/tata.txt"); + EXPECT_FALSE(generator.Generate()); +} + +/****************************************************************************************/ + +TEST_F(GeneratorFixture, Directory) +{ + Generator generator("Fixture/"); + EXPECT_FALSE(generator.Generate()); +} +} // namespace Bin2CPP diff --git a/Bin2CPP/Tests/Bin2CPPLib/Log.cpp b/Bin2CPP/Tests/Bin2CPPLib/Log.cpp new file mode 100644 index 0000000..c6177f1 --- /dev/null +++ b/Bin2CPP/Tests/Bin2CPPLib/Log.cpp @@ -0,0 +1,76 @@ +/********************************************************************* + * \file Log.cpp + * + * \author Romain BOULLARD + * \date February 2026 + *********************************************************************/ +#include + +#include + +#include + +namespace Bin2CPP +{ +class LogFixture: public ::testing::Test +{ + protected: + Log m_log; +}; + +/****************************************************************************************/ + +TEST_F(LogFixture, GetLogger_ShouldReturnTheLogger) +{ + EXPECT_TRUE(m_log.GetLogger()); +} + +/****************************************************************************************/ + +TEST_F(LogFixture, LogDebug) +{ + Singleton::Lifetime singletonLifetime; + BIN2CPP_LOG_DEBUG("Hello"); +} + +/****************************************************************************************/ + +TEST_F(LogFixture, LogTrace) +{ + Singleton::Lifetime singletonLifetime; + + BIN2CPP_LOG_TRACE("Hello"); +} + +/****************************************************************************************/ + +TEST_F(LogFixture, LogInfo) +{ + Singleton::Lifetime singletonLifetime; + BIN2CPP_LOG_INFO("Hello"); +} + +/****************************************************************************************/ + +TEST_F(LogFixture, LogWarn) +{ + Singleton::Lifetime singletonLifetime; + BIN2CPP_LOG_WARN("Hello"); +} + +/****************************************************************************************/ + +TEST_F(LogFixture, LogError) +{ + Singleton::Lifetime singletonLifetime; + BIN2CPP_LOG_ERROR("Hello"); +} + +/****************************************************************************************/ + +TEST_F(LogFixture, LogFatal) +{ + Singleton::Lifetime singletonLifetime; + BIN2CPP_LOG_FATAL("Hello"); +} +} // namespace Bin2CPP diff --git a/Bin2CPP/Tests/Bin2CPPLib/Singleton.cpp b/Bin2CPP/Tests/Bin2CPPLib/Singleton.cpp new file mode 100644 index 0000000..490d7b5 --- /dev/null +++ b/Bin2CPP/Tests/Bin2CPPLib/Singleton.cpp @@ -0,0 +1,39 @@ +/********************************************************************* + * \file Singleton.cpp + * + * \author Romain BOULLARD + * \date February 2026 + *********************************************************************/ +#include + +#include + +namespace Bin2CPP +{ +class SingletonFixture: public ::testing::Test +{ + protected: + Singleton::Lifetime m_lifetime {8}; +}; + +/****************************************************************************************/ + +TEST_F(SingletonFixture, HasInstance_ShouldReturnTrueIfSingletonIsInitialized) +{ + EXPECT_TRUE(Singleton::HasInstance()); +} + +/****************************************************************************************/ + +TEST_F(SingletonFixture, HasInstance_ShouldReturnFaleIfSingletonIsNotInitialized) +{ + EXPECT_FALSE(Singleton::HasInstance()); +} + +/****************************************************************************************/ + +TEST_F(SingletonFixture, Instance_ShouldReturnTheInstance) +{ + EXPECT_EQ(Singleton::Instance(), 8); +} +} // namespace Bin2CPP diff --git a/Bin2CPP/Tests/CMakeLists.txt b/Bin2CPP/Tests/CMakeLists.txt new file mode 100644 index 0000000..8fa1d8a --- /dev/null +++ b/Bin2CPP/Tests/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/Bin2CPPLib) diff --git a/CMake/Bin2CPPTargets.cmake b/CMake/Bin2CPPTargets.cmake new file mode 100644 index 0000000..9dfd8e6 --- /dev/null +++ b/CMake/Bin2CPPTargets.cmake @@ -0,0 +1,23 @@ +if(NOT TARGET Bin2CPP) + if(CMAKE_CROSSCOMPILING) + find_program(BIN2CPP_EXECUTABLE + NAMES Bin2CPP + PATHS ENV PATH + NO_DEFAULT_PATH + ) + else() + find_program(BIN2CPP_EXECUTABLE + NAMES Bin2CPP + PATHS "${CMAKE_CURRENT_LIST_DIR}" + NO_DEFAULT_PATH + ) + endif() + + if(BIN2CPP_EXECUTABLE) + get_filename_component(BIN2CPP_EXECUTABLE "${BIN2CPP_EXECUTABLE}" ABSOLUTE) + add_executable(Bin2CPP IMPORTED) + set_property(TARGET Bin2CPP PROPERTY IMPORTED_LOCATION ${BIN2CPP_EXECUTABLE}) + else() + message(WARNING "Bin2CPP executable not found in ${CMAKE_CURRENT_LIST_DIR}") + endif() +endif() diff --git a/CMake/CustomTargets.cmake b/CMake/CustomTargets.cmake new file mode 100644 index 0000000..9f402e0 --- /dev/null +++ b/CMake/CustomTargets.cmake @@ -0,0 +1,19 @@ +add_library(Bin2CPPCompileAndLinkFlags INTERFACE) + +target_compile_options(Bin2CPPCompileAndLinkFlags INTERFACE + $<$:/W4 /WX> + $<$:-Wall -Wextra -Wpedantic -Werror> + $<$,$>:-fprofile-instr-generate -fcoverage-mapping> +) + +target_link_options(Bin2CPPCompileAndLinkFlags INTERFACE + $<$,$>:-fprofile-instr-generate> +) + +target_compile_definitions(Bin2CPPCompileAndLinkFlags INTERFACE + $<$:BIN2CPP_OPTIMIZED> + $<$:BIN2CPP_NOT_OPTIMIZED> + $<$:BIN2CPP_WINDOWS> + $<$:BIN2CPP_LINUX> + $<$:NOMINMAX> + $<$:WIN32_LEAN_AND_MEAN>) \ No newline at end of file diff --git a/CMake/FindDependencies.cmake b/CMake/FindDependencies.cmake new file mode 100644 index 0000000..1b7f2da --- /dev/null +++ b/CMake/FindDependencies.cmake @@ -0,0 +1,14 @@ +find_package(Python3 COMPONENTS Interpreter Development) + +if(CMAKE_SYSTEM_NAME STREQUAL "Linux") + set(THREADS_PREFER_PTHREAD_FLAG ON) + find_package(Threads REQUIRED) +endif() + +find_package(magic_enum REQUIRED) +find_package(CLI11 REQUIRED) +find_package(quill REQUIRED) + +if(BUILD_TESTS) + find_package(GTest REQUIRED) +endif() \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..a0d6a8e --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,36 @@ +cmake_minimum_required(VERSION 3.26) + +project(Bin2CPP VERSION 1.0.0 + DESCRIPTION "Bin2CPP" + LANGUAGES CXX) + +set(CMAKE_POLICY_DEFAULT_CMP0077 NEW) + +get_property(IS_MULTI_CONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) + +include(${CMAKE_SOURCE_DIR}/CMake/CustomTargets.cmake) +include(${CMAKE_SOURCE_DIR}/CMake/FindDependencies.cmake) + +set(CMAKE_CONFIGURATION_TYPES "Release;RelWithDebInfo;Debug" CACHE STRING "" FORCE) + +option(BUILD_TESTS OFF) +option(ASAN OFF) +option(COVERAGE OFF) + +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) +set(CMAKE_OPTIMIZE_DEPENDENCIES 1) + +if(BUILD_TESTS) + enable_testing() +endif() + +set_property(GLOBAL PROPERTY USE_FOLDERS ON) + +SET(CMAKE_SKIP_BUILD_RPATH FALSE) +SET(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE) +SET(CMAKE_INSTALL_RPATH "\${ORIGIN}") + +add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/Bin2CPP/Sources) +if(${BUILD_TESTS}) + add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/Bin2CPP/Tests) +endif() \ No newline at end of file diff --git a/ConanProfiles/Toolchains/ipo.cmake b/ConanProfiles/Toolchains/ipo.cmake new file mode 100644 index 0000000..7e83475 --- /dev/null +++ b/ConanProfiles/Toolchains/ipo.cmake @@ -0,0 +1,5 @@ +include_guard() + +set(CMAKE_POLICY_DEFAULT_CMP0069 NEW) + +set(CMAKE_INTERPROCEDURAL_OPTIMIZATION_RELEASE TRUE) \ No newline at end of file diff --git a/ConanProfiles/Toolchains/ipo.ini b/ConanProfiles/Toolchains/ipo.ini new file mode 100644 index 0000000..bf155da --- /dev/null +++ b/ConanProfiles/Toolchains/ipo.ini @@ -0,0 +1,2 @@ +[built-in options] +b_lto = true \ No newline at end of file diff --git a/ConanProfiles/Toolchains/mold.cmake b/ConanProfiles/Toolchains/mold.cmake new file mode 100644 index 0000000..8d48ddf --- /dev/null +++ b/ConanProfiles/Toolchains/mold.cmake @@ -0,0 +1,9 @@ +include_guard() + +find_program(MOLD_PROGRAM mold) +if (MOLD_PROGRAM) + set(CMAKE_LINKER_TYPE MOLD) + message(STATUS "mold linker found: ${MOLD_PROGRAM}") +else () + message(WARNING "mold linker not found") +endif () \ No newline at end of file diff --git a/ConanProfiles/Tools/clang b/ConanProfiles/Tools/clang new file mode 100644 index 0000000..a840b7c --- /dev/null +++ b/ConanProfiles/Tools/clang @@ -0,0 +1,19 @@ +[settings] +os=Linux +arch=x86_64 +compiler=clang +compiler.version=20 +compiler.libcxx=libstdc++11 +compiler.cppstd=20 +compiler.cstd=17 +compiler.runtime=static +build_type=Release + +[conf] +tools.cmake.cmaketoolchain:user_toolchain+={{profile_dir}}/../Toolchains/ipo.cmake +tools.meson.mesontoolchain:extra_machine_files+={{profile_dir}}/../Toolchains/ipo.ini + +tools.system.package_manager:mode=install +tools.system.package_manager:sudo=True + +tools.build:compiler_executables={"c": "clang", "cpp": "clang++"} \ No newline at end of file diff --git a/ConanProfiles/Tools/msvc b/ConanProfiles/Tools/msvc new file mode 100644 index 0000000..9485978 --- /dev/null +++ b/ConanProfiles/Tools/msvc @@ -0,0 +1,18 @@ +[settings] +os=Windows +arch=x86_64 +compiler=msvc +compiler.version=195 +compiler.cppstd=20 +compiler.cstd=17 +compiler.runtime=static +build_type=Release + +[conf] +tools.cmake.cmaketoolchain:user_toolchain+={{profile_dir}}/../Toolchains/ipo.cmake +tools.meson.mesontoolchain:extra_machine_files+={{profile_dir}}/../Toolchains/ipo.ini + +tools.build:cflags=["/Zc:preprocessor", "/Zc:__STDC__", "/D_CRT_DECLARE_NONSTDC_NAMES=1"] +tools.build:cxxflags=["/Zc:preprocessor", "/permissive-", "/Zc:__cplusplus", "/Zc:enumTypes", "/Zc:templateScope"] + +tools.env.virtualenv:powershell=powershell.exe \ No newline at end of file diff --git a/ConanProfiles/clang b/ConanProfiles/clang new file mode 100644 index 0000000..06c841f --- /dev/null +++ b/ConanProfiles/clang @@ -0,0 +1,30 @@ +[settings] +os=Linux +arch=x86_64 +compiler=clang +compiler.version=20 +compiler.libcxx=libstdc++11 +compiler.cppstd=20 +compiler.cstd=17 +compiler.runtime=static +build_type=Release + +[conf] +tools.cmake.cmaketoolchain:user_toolchain+={{profile_dir}}/Toolchains/mold.cmake +tools.cmake.cmaketoolchain:user_toolchain+={{profile_dir}}/Toolchains/ipo.cmake +tools.meson.mesontoolchain:extra_machine_files+={{profile_dir}}/Toolchains/ipo.ini + +tools.system.package_manager:mode=install +tools.system.package_manager:sudo=True + +tools.build:compiler_executables={"c": "clang", "cpp": "clang++"} + +tools.cmake.cmaketoolchain:generator=Ninja + +[tool_requires] +!cmake/*: cmake/4.3.2 +!mold/*: mold/2.41.0@bigfootdev/main +!ninja/*: ninja/1.13.2 + +[options] +bin2cpp/*:build_tests=True diff --git a/ConanProfiles/clang_asan b/ConanProfiles/clang_asan new file mode 100644 index 0000000..7a2ce47 --- /dev/null +++ b/ConanProfiles/clang_asan @@ -0,0 +1,35 @@ +[settings] +os=Linux +arch=x86_64 +compiler=clang +compiler.version=20 +compiler.libcxx=libstdc++11 +compiler.cppstd=20 +compiler.cstd=17 +compiler.runtime=static +build_type=Debug + +[conf] +tools.cmake.cmaketoolchain:user_toolchain+={{profile_dir}}/Toolchains/mold.cmake + +tools.system.package_manager:mode=install +tools.system.package_manager:sudo=True + +tools.build:exelinkflags=["-fsanitize=address,undefined,leak"] +tools.build:sharedlinkflags=["-fsanitize=address,undefined,leak"] + +tools.build:cflags=["-fsanitize=address,undefined,leak", "-fno-sanitize-recover=all"] +tools.build:cxxflags=["-fsanitize=address,undefined,leak", "-fno-sanitize-recover=all"] + +tools.cmake.cmaketoolchain:generator=Ninja + +tools.build:compiler_executables={"c": "clang", "cpp": "clang++"} + +[tool_requires] +!cmake/*: cmake/4.3.2 +!mold/*: mold/2.41.0@bigfootdev/main +!ninja/*: ninja/1.13.2 + +[options] +bin2cpp/*:asan=True +bin2cpp/*:build_tests=True diff --git a/ConanProfiles/clang_coverage b/ConanProfiles/clang_coverage new file mode 100644 index 0000000..986975d --- /dev/null +++ b/ConanProfiles/clang_coverage @@ -0,0 +1,29 @@ +[settings] +os=Linux +arch=x86_64 +compiler=clang +compiler.version=20 +compiler.libcxx=libstdc++11 +compiler.cppstd=20 +compiler.cstd=17 +compiler.runtime=static +build_type=Debug + +[conf] +tools.cmake.cmaketoolchain:user_toolchain+={{profile_dir}}/Toolchains/mold.cmake + +tools.system.package_manager:mode=install +tools.system.package_manager:sudo=True + +tools.cmake.cmaketoolchain:generator=Ninja + +tools.build:compiler_executables={"c": "clang", "cpp": "clang++"} + +[tool_requires] +!cmake/*: cmake/4.3.2 +!mold/*: mold/2.41.0@bigfootdev/main +!ninja/*: ninja/1.13.2 + +[options] +bin2cpp/*:build_tests=True +bin2cpp/*:coverage=True diff --git a/ConanProfiles/msvc b/ConanProfiles/msvc new file mode 100644 index 0000000..216c0fe --- /dev/null +++ b/ConanProfiles/msvc @@ -0,0 +1,24 @@ +[settings] +os=Windows +arch=x86_64 +compiler=msvc +compiler.version=195 +compiler.cppstd=20 +compiler.cstd=17 +compiler.runtime=static +build_type=Release + +[conf] +tools.cmake.cmaketoolchain:user_toolchain+={{profile_dir}}/Toolchains/ipo.cmake +tools.meson.mesontoolchain:extra_machine_files+={{profile_dir}}/Toolchains/ipo.ini + +tools.build:cflags=["/Zc:preprocessor", "/Zc:__STDC__", "/D_CRT_DECLARE_NONSTDC_NAMES=1"] +tools.build:cxxflags=["/Zc:preprocessor", "/permissive-", "/Zc:__cplusplus", "/Zc:enumTypes", "/Zc:templateScope", "/Zc:throwingNew"] + +tools.env.virtualenv:powershell=powershell.exe + +[tool_requires] +!cmake/*: cmake/4.3.2 + +[options] +bin2cpp/*:build_tests=True diff --git a/ConanProfiles/msvc_asan b/ConanProfiles/msvc_asan new file mode 100644 index 0000000..cd32bdf --- /dev/null +++ b/ConanProfiles/msvc_asan @@ -0,0 +1,22 @@ +[settings] +os=Windows +arch=x86_64 +compiler=msvc +compiler.version=195 +compiler.cppstd=20 +compiler.cstd=17 +compiler.runtime=static +build_type=Debug + +[conf] +tools.build:cflags=["/Zc:preprocessor", "/Zc:__STDC__", "/D_CRT_DECLARE_NONSTDC_NAMES=1", "/fsanitize=address"] +tools.build:cxxflags=["/Zc:preprocessor", "/permissive-", "/Zc:__cplusplus", "/Zc:enumTypes", "/Zc:templateScope", "/Zc:throwingNew", "/fsanitize=address"] + +tools.env.virtualenv:powershell=powershell.exe + +[tool_requires] +!cmake/*: cmake/4.3.2 + +[options] +bin2cpp/*:asan=True +bin2cpp/*:build_tests=True diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..e05d49d --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022-2026 Romain BOULLARD + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/conanfile.py b/conanfile.py new file mode 100644 index 0000000..b4752e4 --- /dev/null +++ b/conanfile.py @@ -0,0 +1,84 @@ +from conan import ConanFile +from conan.tools.cmake import CMakeToolchain, CMake, cmake_layout +from conan.tools.files import copy +import os + +required_conan_version = ">=1.33.0" + +class Bin2CPP(ConanFile): + name = "bin2cpp" + homepage = "https://git.romainboullard.com/rboullard/Bin2CPP" + description = "A utility that converts files to CPP headers" + topics = ("utility") + license = "MIT" + version = "1.0.0" + package_type = "application" + + # Binary configuration + settings = "os", "compiler", "build_type", "arch" + options = { + "shared": [True, False], + "fPIC": [True, False], + "build_tests": [True, False], + "asan": [True, False], + "coverage": [True, False] + } + default_options = { + "shared": False, + "fPIC": True, + "build_tests": False, + "asan": False, + "coverage": False + } + + generators = "CMakeDeps" + + def export_sources(self): + copy(self, "CMakeLists.txt", self.recipe_folder, self.export_sources_folder) + copy(self, os.path.join("Bin2CPP", "*"), self.recipe_folder, self.export_sources_folder) + copy(self, os.path.join("CMake", "*"), self.recipe_folder, self.export_sources_folder) + + def layout(self): + cmake_layout(self) + + def configure(self): + if self.settings.os == "Windows": + del self.options.fPIC + + def requirements(self): + self.requires("quill/11.0.2", transitive_headers=True) + self.requires("magic_enum/0.9.7", transitive_headers=True) + self.requires("cli11/2.6.1@bigfootdev/main") + + if(self.options.build_tests): + self.test_requires("gtest/1.17.0") + + def generate(self): + tc = CMakeToolchain(self) + + tc.variables["BUILD_TESTS"] = self.options.build_tests + tc.variables["ASAN"] = self.options.asan + tc.variables["COVERAGE"] = self.options.coverage + + tc.generate() + + def build(self): + cmake = CMake(self) + cmake.configure() + cmake.build() + + def package(self): + copy(self, pattern="LICENSE", dst=os.path.join(self.package_folder, "licenses"), src=self.source_folder) + cmake = CMake(self) + cmake.install() + + copy(self, "Bin2CPPTargets.cmake", + src=os.path.join(self.source_folder, "CMake"), + dst=os.path.join(self.package_folder, "bin")) + + @property + def _module_path(self): + return os.path.join("bin", "Bin2CPPTargets.cmake") + + def package_info(self): + self.cpp_info.set_property("cmake_build_modules", [self._module_path]) \ No newline at end of file diff --git a/format.bat b/format.bat new file mode 100644 index 0000000..54d904f --- /dev/null +++ b/format.bat @@ -0,0 +1,71 @@ +@echo off +SETLOCAL ENABLEDELAYEDEXPANSION + +REM ========================= +REM Variables +REM ========================= +SET FMT=clang-format +SET MODE= +SET EXIT_CODE=0 + +REM ========================= +REM Parse arguments +REM ========================= +:parse_args +IF "%~1"=="" GOTO end_parse_args +IF "%~1"=="--check" ( + SET MODE=check +) ELSE IF "%~1"=="--fix" ( + SET MODE=fix +) ELSE ( + REM Accumulate directories + SET DIRS=!DIRS! "%~1" +) +SHIFT +GOTO parse_args +:end_parse_args + +IF "%MODE%"=="" ( + ECHO Usage: %~nx0 --check|--fix [ ...] + EXIT /B 1 +) + +IF "%DIRS%"=="" ( + ECHO Please provide at least one directory. + EXIT /B 1 +) + +REM ========================= +REM Iterate over directories +REM ========================= +FOR %%D IN (%DIRS%) DO ( + IF NOT EXIST "%%~D" ( + ECHO %%~D is not a valid directory. + SET EXIT_CODE=1 + GOTO :continue_dirs + ) + + REM Recursively find source files + FOR /R "%%~D" %%F IN (*.h *.hpp *.c *.cpp *.m *.mm) DO ( + SET FILE=%%F + REM Skip *_generated* files + ECHO !FILE! | FINDSTR /I "_generated" >nul + IF ERRORLEVEL 1 ( + IF "%MODE%"=="fix" ( + ECHO Formatting !FILE! + %FMT% -i "!FILE!" + ) ELSE ( + ECHO Checking !FILE! + %FMT% --dry-run --Werror "!FILE!" 2>nul + IF ERRORLEVEL 1 ( + REM clang-format will already print diagnostics + SET EXIT_CODE=1 + ) + ) + ) + ) + + :continue_dirs +) + +EXIT /B %EXIT_CODE% diff --git a/format.sh b/format.sh new file mode 100644 index 0000000..eb8f665 --- /dev/null +++ b/format.sh @@ -0,0 +1,68 @@ +#!/bin/bash +set -euo pipefail + +FMT="clang-format" +MODE="" +DIRS=() +EXIT_CODE=0 + +# ========================= +# Parse arguments +# ========================= +for arg in "$@"; do + case "$arg" in + --check) + MODE="check" + ;; + --fix) + MODE="fix" + ;; + *) + DIRS+=("$arg") + ;; + esac +done + +# Ensure mode is set +if [[ -z "$MODE" ]]; then + echo "Usage: $0 --check|--fix [ ...]" + exit 1 +fi + +# Ensure at least one directory +if [[ ${#DIRS[@]} -eq 0 ]]; then + echo "Please provide at least one directory." + exit 1 +fi + +# ========================= +# Process directories +# ========================= +for DIR in "${DIRS[@]}"; do + if [[ ! -d "$DIR" ]]; then + echo "$DIR is not a valid directory." + EXIT_CODE=1 + continue + fi + + # Find all source files safely (null-separated) + while IFS= read -r -d '' FILE; do + [[ "$FILE" == *_generated* ]] && continue + + if [[ "$MODE" == "fix" ]]; then + echo "Formatting $FILE" + "$FMT" -i "$FILE" + else + echo "Checking $FILE" + # Check mode: clang-format diagnostic only + if ! "$FMT" --dry-run --Werror "$FILE"; then + # clang-format prints: file:line:col: error ... + EXIT_CODE=1 + fi + fi + done < <( + find "$DIR" \( -name '*.h' -o -name '*.hpp' -o -name '*.c' -o -name '*.cpp' -o -name '*.m' -o -name '*.mm' \) -print0 + ) +done + +exit $EXIT_CODE diff --git a/generate_bin2cpp.bat b/generate_bin2cpp.bat new file mode 100644 index 0000000..d996fbf --- /dev/null +++ b/generate_bin2cpp.bat @@ -0,0 +1,60 @@ +@echo off +setlocal + +REM ─── Validate argument ─────────────────────────────────────────────────────── +if "%~1"=="force" set "build_option=--build=*" & goto :start +if "%~1"=="missing" set "build_option=--build=missing" & goto :start + +echo Usage: %~n0 [force^|missing] +echo force - Rebuild all packages from source +echo missing - Only build packages not already cached +exit /b 1 + +:start +REM ─── Register remote (skip if already registered) ──────────────────────────── +conan remote list | findstr /i "bigfootpackages" >nul 2>&1 +if errorlevel 1 ( + echo Adding Conan remote: bigfootpackages + conan remote add bigfootpackages https://conan.romainboullard.com/artifactory/api/conan/BigfootPackages + if errorlevel 1 ( + echo ERROR: Failed to add Conan remote. + exit /b 1 + ) +) else ( + echo Conan remote 'bigfootpackages' already registered, skipping. +) + +REM ─── Shared flags ──────────────────────────────────────────────────────────── +set "conan_common=--remote=bigfootpackages -pr:h=./ConanProfiles/msvc -pr:b=./ConanProfiles/Tools/msvc -of build" + +REM ─── Install dependencies for each build type ──────────────────────────────── +for %%C in (Release RelWithDebInfo Debug) do ( + echo. + echo [%%C] Installing dependencies... + conan install . %conan_common% %build_option% -s:h build_type=%%C + if errorlevel 1 ( + echo ERROR: conan install failed for build type %%C + exit /b 1 + ) + echo [%%C] Done. +) + +echo. +echo All build types installed successfully. + +REM ─── Activate build environment and run CMake ──────────────────────────────── +echo. +echo Activating build environment and configuring CMake... +mkdir graphviz 2>nul + +powershell -ExecutionPolicy Bypass -Command "& 'build/build/generators/conanbuild.ps1'; cmake -S . -B build --toolchain build/build/generators/conan_toolchain.cmake --graphviz=graphviz/graph.dot; if ($LASTEXITCODE -ne 0) { Write-Host 'ERROR: CMake configuration failed'; exit 1 }" + +if errorlevel 1 ( + echo ERROR: Build environment or CMake step failed. + exit /b 1 +) + +echo. +echo CMake configuration successful. + +endlocal \ No newline at end of file diff --git a/generate_bin2cpp.sh b/generate_bin2cpp.sh new file mode 100644 index 0000000..a91f6b8 --- /dev/null +++ b/generate_bin2cpp.sh @@ -0,0 +1,100 @@ +#!/usr/bin/env bash +set -euo pipefail + +# ─── Validate arguments ─────────────────────────────────────────────────────── +if [[ $# -lt 1 ]]; then + echo "Usage: $(basename "$0") [force|missing] [Release|RelWithDebInfo|Debug]" + echo " force - Rebuild all packages from source" + echo " missing - Only build packages not already cached" + echo " build_type (optional) - One of: Release, RelWithDebInfo, Debug" + echo " If omitted, all build types are processed" + exit 1 +fi + +case "${1:-}" in + force) build_option="--build=*" ;; + missing) build_option="--build=missing" ;; + *) + echo "Usage: $(basename "$0") [force|missing] [Release|RelWithDebInfo|Debug]" + echo " force - Rebuild all packages from source" + echo " missing - Only build packages not already cached" + exit 1 + ;; +esac + +# ─── Validate optional build_type argument ──────────────────────────────────── +all_build_types=(Release RelWithDebInfo Debug) + +if [[ -n "${2:-}" ]]; then + case "${2}" in + Release|RelWithDebInfo|Debug) + build_types=("${2}") + ;; + *) + echo "ERROR: Invalid build type '${2}'. Must be one of: Release, RelWithDebInfo, Debug" + exit 1 + ;; + esac +else + build_types=("${all_build_types[@]}") +fi + +# ─── Register remote (skip if already registered) ───────────────────────────── +if conan remote list 2>/dev/null | grep -qi "bigfootpackages"; then + echo "Conan remote 'bigfootpackages' already registered, skipping." +else + echo "Adding Conan remote: bigfootpackages" + if ! conan remote add bigfootpackages https://conan.romainboullard.com/artifactory/api/conan/BigfootPackages; then + echo "ERROR: Failed to add Conan remote." + exit 1 + fi +fi + +# ─── Shared flags ───────────────────────────────────────────────────────────── +conan_common=( + --remote=bigfootpackages + -pr:h=./ConanProfiles/clang + -pr:b=./ConanProfiles/Tools/clang + -of build +) + +# ─── Install dependencies for each build type ───────────────────────────────── +for build_type in "${build_types[@]}"; do + echo + echo "[${build_type}] Installing dependencies..." + if ! conan install . "${conan_common[@]}" ${build_option} -s:h "build_type=${build_type}"; then + echo "ERROR: conan install failed for build type ${build_type}" + exit 1 + fi + echo "[${build_type}] Done." +done + +echo +echo "All selected build types installed successfully." + +# ─── Configure CMake for each build type ───────────────────────────────────── +echo +echo "Configuring CMake for each build type..." + +for build_type in "${build_types[@]}"; do + echo + echo "[${build_type}] Activating build environment..." + # shellcheck disable=SC1090 + source "build/build/${build_type}/generators/conanbuild.sh" + + mkdir -p "graphviz/${build_type}" + + echo "[${build_type}] Running CMake..." + if ! cmake -S . -B "build/${build_type}" \ + --toolchain "build/build/${build_type}/generators/conan_toolchain.cmake" \ + -DCMAKE_BUILD_TYPE="${build_type}" \ + -G "Ninja" \ + --graphviz="graphviz/${build_type}/graph.dot"; then + echo "ERROR: CMake configuration failed for build type ${build_type}." + exit 1 + fi + echo "[${build_type}] Done." +done + +echo +echo "CMake configuration successful." \ No newline at end of file diff --git a/sonar-project.properties b/sonar-project.properties new file mode 100644 index 0000000..7f7835c --- /dev/null +++ b/sonar-project.properties @@ -0,0 +1,14 @@ +sonar.projectKey=Bin2CPP + +sonar.projectVersion=1.0.0 + +sonar.sourceEncoding=UTF-8 + +sonar.sources=Bin2CPP/Sources +sonar.tests=Bin2CPP/Tests + +sonar.cxx.file.suffixes=.hpp,.cpp,.h +sonar.cxx.clangtidy.reportPaths=tidy_result.txt +sonar.cxx.infer.reportPaths=infer-out/report.json + +sonar.cxx.jsonCompilationDatabase.analyzeOnlyContainedFiles=True diff --git a/test_package/CMakeLists.txt b/test_package/CMakeLists.txt new file mode 100644 index 0000000..20ef5be --- /dev/null +++ b/test_package/CMakeLists.txt @@ -0,0 +1,7 @@ +cmake_minimum_required(VERSION 3.15) +project(test_package LANGUAGES C) + +find_package(Bin2CPP REQUIRED CONFIG) + +find_program(BIN2CPP_EXECUTABLE NAMES Bin2CPP) +execute_process(COMMAND ${BIN2CPP_EXECUTABLE} -h) diff --git a/test_package/conanfile.py b/test_package/conanfile.py new file mode 100644 index 0000000..c1dcff9 --- /dev/null +++ b/test_package/conanfile.py @@ -0,0 +1,18 @@ +from conan import ConanFile +from conan.tools.build import can_run +from conan.tools.cmake import CMake, cmake_layout +import os + + +class TestPackageConan(ConanFile): + settings = "os", "arch", "compiler", "build_type" + generators = "CMakeToolchain", "CMakeDeps", "VirtualRunEnv" + + def layout(self): + cmake_layout(self) + + def requirements(self): + self.requires(self.tested_reference_str) + + def test(self): + self.run("Bin2CPP --help", env="conanrun")