diff --git a/README.md b/README.md index 05751ee..c432065 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,86 @@ # Dropshell Build +A Docker-based build system for creating statically-linked C++ executables using Alpine Linux and musl libc. + +## Overview + +This repository provides a comprehensive C++ development environment with: + +1. **dropshell-build-base**: A base image with C++ toolchain and libraries +2. **dropshell-build**: Multi-stage Docker build system for C++ projects +3. **Test projects**: Sample applications demonstrating the build system + +## Features + +### Libraries Available + +The base image includes statically-linked versions of: + +- **Web Framework**: Drogon with async HTTP capabilities +- **Database Support**: PostgreSQL (libpq), MySQL/MariaDB, SQLite3 +- **HTTP Clients**: + - Custom Drogon-based HTTP utilities (see `ipdemo`) + - CPR (C++ Requests) library for simplified HTTP requests +- **Networking**: cURL, c-ares, OpenSSL +- **JSON**: nlohmann/json, jsoncpp +- **Compression**: zlib, zstd, lzma +- **Logging**: spdlog, fmt +- **Utilities**: ccache, mold linker + +### HTTP Client Options + +Two HTTP client approaches are available: + +1. **Drogon HTTP Utilities** (see `tests/ipdemo/src/http_utils.hpp`): + ```cpp + auto response = http_get("example.com", "/api/endpoint", true, 10.0); + if (response.success && response.status_code == 200) { + std::cout << response.body << std::endl; + } + ``` + +2. **CPR Library** (C++ Requests): + ```cpp + #include + cpr::Response r = cpr::Get(cpr::Url{"https://example.com/api"}); + if (r.status_code == 200) { + std::cout << r.text << std::endl; + } + ``` + +## Quick Start + +### Building the Base Image + +```bash +cd build-base +./build.sh +``` + +### Building a Project + +```bash +./build.sh +``` + +### Testing + +```bash +./test.sh +``` + +## Project Structure + +### Test Projects + +- **ipdemo**: Demonstrates Drogon HTTP utilities and JSON processing +- **test_libs**: Tests all available libraries (fmt, spdlog, SQLite3, etc.) +- **cprdemo**: Example using CPR library for HTTP requests (work in progress) + +### Environment Variables + +- `CMAKE_BUILD_TYPE`: "Debug" or "Release" (default: Debug) +- `PROJECT`: Project name to build (default: ipdemo) + ## Installation diff --git a/build-base/Dockerfile.dropshell-build-base b/build-base/Dockerfile.dropshell-build-base index fa1d219..10d2d03 100644 --- a/build-base/Dockerfile.dropshell-build-base +++ b/build-base/Dockerfile.dropshell-build-base @@ -46,7 +46,9 @@ RUN apk add --no-cache \ xz-libs \ xz-static \ zlib-dev \ - zlib-static + zlib-static \ + zstd-dev \ + zstd-static SHELL ["/bin/bash", "-c"] @@ -132,11 +134,34 @@ WORKDIR /tmp RUN curl -LO https://curl.se/download/curl-${CURL_VERSION}.tar.gz && \ tar xzf curl-${CURL_VERSION}.tar.gz && \ cd curl-${CURL_VERSION} && \ - ./configure --disable-shared --enable-static --with-ssl=/usr/local --prefix=/usr/local && \ + ./configure --disable-shared --enable-static --with-ssl=/usr/local --prefix=/usr/local \ + --with-zlib --with-zstd && \ make -j$(nproc) && \ make install && \ cd / && rm -rf /tmp/curl-${CURL_VERSION} /tmp/curl-${CURL_VERSION}.tar.gz +# Build CPR (C++ Requests) library statically +ARG CPR_VERSION=1.10.5 +WORKDIR /tmp +RUN curl -LO https://github.com/libcpr/cpr/archive/refs/tags/${CPR_VERSION}.tar.gz && \ + tar xzf ${CPR_VERSION}.tar.gz && \ + cd cpr-${CPR_VERSION} && \ + mkdir build && cd build && \ + cmake .. \ + -DCMAKE_BUILD_TYPE=Release \ + -DBUILD_SHARED_LIBS=OFF \ + -DCMAKE_POSITION_INDEPENDENT_CODE=ON \ + -DCMAKE_INSTALL_PREFIX=/usr/local \ + -DCPR_USE_SYSTEM_CURL=ON \ + -DCPR_ENABLE_SSL=ON \ + -DCURL_INCLUDE_DIR=/usr/local/include \ + -DCURL_LIBRARY=/usr/local/lib/libcurl.a \ + -DOPENSSL_ROOT_DIR=/usr/local \ + -DOPENSSL_USE_STATIC_LIBS=TRUE && \ + make -j$(nproc) && \ + make install && \ + cd / && rm -rf /tmp/cpr-${CPR_VERSION} /tmp/${CPR_VERSION}.tar.gz + ARG MARIADB_CONNECTOR_VERSION=3.4.5 WORKDIR /tmp diff --git a/tests/build.sh b/tests/build.sh index f006fc7..4ce17fb 100755 --- a/tests/build.sh +++ b/tests/build.sh @@ -32,3 +32,6 @@ build_project "ipdemo" echo "Building test_libs..." build_project "test_libs" +echo "Building cprdemo..." +build_project "cprdemo" + diff --git a/tests/cprdemo/CMakeLists.txt b/tests/cprdemo/CMakeLists.txt new file mode 100644 index 0000000..a10c1ae --- /dev/null +++ b/tests/cprdemo/CMakeLists.txt @@ -0,0 +1,63 @@ +cmake_minimum_required(VERSION 3.16) + +# Project setup +if(NOT DEFINED PROJECT_NAME) + message(FATAL_ERROR "PROJECT_NAME is not defined. Pass it via -DPROJECT_NAME=") +endif() + +string(TIMESTAMP PROJECT_VERSION "%Y.%m%d.%H%M") +project(${PROJECT_NAME} VERSION ${PROJECT_VERSION} LANGUAGES CXX) + +# Build configuration +set(CMAKE_CXX_STANDARD 23) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_EXE_LINKER_FLAGS "-static") +set(CMAKE_FIND_LIBRARY_SUFFIXES ".a") +set(BUILD_SHARED_LIBS OFF) +set(CMAKE_PREFIX_PATH /usr/local) + +# Create executable +file(GLOB_RECURSE SOURCES "src/*.cpp") +add_executable(${PROJECT_NAME} ${SOURCES}) + +# Configure version.hpp +configure_file("src/version.hpp.in" "src/autogen/version.hpp" @ONLY) + +# Pre-build script +add_custom_target(run_prebuild_script ALL + COMMAND bash ${CMAKE_CURRENT_SOURCE_DIR}/cmake_prebuild.sh + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) +add_dependencies(${PROJECT_NAME} run_prebuild_script) + +# Include directories +target_include_directories(${PROJECT_NAME} PRIVATE + ${CMAKE_CURRENT_BINARY_DIR}/src/autogen + src) + +# Find packages +find_package(OpenSSL REQUIRED) +find_package(PkgConfig REQUIRED) +find_package(nlohmann_json REQUIRED) + +# Find CURL manually since we built it without CMake targets +find_library(CURL_LIBRARY NAMES curl libcurl PATHS /usr/local/lib NO_DEFAULT_PATH) +find_path(CURL_INCLUDE_DIR NAMES curl/curl.h PATHS /usr/local/include NO_DEFAULT_PATH) + +# Create CURL target if not found +if(NOT TARGET CURL::libcurl) + add_library(CURL::libcurl STATIC IMPORTED) + set_target_properties(CURL::libcurl PROPERTIES + IMPORTED_LOCATION ${CURL_LIBRARY} + INTERFACE_INCLUDE_DIRECTORIES ${CURL_INCLUDE_DIR} + INTERFACE_LINK_LIBRARIES "/usr/lib/libz.a;/usr/lib/libzstd.a" + ) +endif() + +# Find CPR after CURL target is available +find_package(cpr REQUIRED) + +# Link libraries +target_link_libraries(${PROJECT_NAME} PRIVATE + nlohmann_json::nlohmann_json + cpr::cpr + lzma dl) \ No newline at end of file diff --git a/tests/cprdemo/cmake_prebuild.sh b/tests/cprdemo/cmake_prebuild.sh new file mode 100755 index 0000000..ec4d6a8 --- /dev/null +++ b/tests/cprdemo/cmake_prebuild.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +echo "CPR Demo Pre-build Script" +echo "=========================" +echo "Build time: $(date)" +echo "Working directory: $(pwd)" +echo "CMAKE args: $@" +echo "CPR Demo ready for build" \ No newline at end of file diff --git a/tests/cprdemo/src/assert.hpp b/tests/cprdemo/src/assert.hpp new file mode 100644 index 0000000..f27bf1a --- /dev/null +++ b/tests/cprdemo/src/assert.hpp @@ -0,0 +1,13 @@ +#pragma once + +#include +#include + +#define ASSERT(condition, message) \ + do { \ + if (!(condition)) { \ + std::cerr << "Assertion failed: " << message << std::endl; \ + std::cerr << "File: " << __FILE__ << ", Line: " << __LINE__ << std::endl; \ + throw std::runtime_error(message); \ + } \ + } while (false) diff --git a/tests/cprdemo/src/main.cpp b/tests/cprdemo/src/main.cpp new file mode 100644 index 0000000..feff3b3 --- /dev/null +++ b/tests/cprdemo/src/main.cpp @@ -0,0 +1,33 @@ +#include +#include +#include + +#include "version.hpp" +#include "assert.hpp" + +void crashy() { + ASSERT(false, "SUCCESS!"); +} + +int main() { + std::cout << "cprdemo version: " << cprdemo::VERSION << std::endl; + std::cout << std::endl; + std::cout << "Retrieving IP address using CPR..." << std::endl; + + // Use CPR to make HTTP GET request + cpr::Response r = cpr::Get(cpr::Url{"https://ipinfo.io/ip"}); + + ASSERT(r.status_code == 200, "Failed to get IP"); + + nlohmann::json j; + j["ip"] = r.text; + j["status"] = r.status_code; + + std::cout << j.dump(4) << std::endl; + + std::cout << "Done" << std::endl; + + crashy(); + + return 0; +} \ No newline at end of file diff --git a/tests/cprdemo/src/version.hpp.in b/tests/cprdemo/src/version.hpp.in new file mode 100644 index 0000000..a9e3449 --- /dev/null +++ b/tests/cprdemo/src/version.hpp.in @@ -0,0 +1,5 @@ +#pragma once + +namespace @PROJECT_NAME@ { + constexpr const char* VERSION = "@PROJECT_VERSION@"; +} \ No newline at end of file diff --git a/tests/test.sh b/tests/test.sh index e6621b0..bd5b5b2 100755 --- a/tests/test.sh +++ b/tests/test.sh @@ -28,3 +28,13 @@ else echo "ipdemo binary not found - run ./build.sh first" fi +# Test cprdemo +if [ -f "${SCRIPT_DIR}/output/cprdemo" ]; then + echo "" + echo "Testing cprdemo..." + echo "Running cprdemo..." + # Run cprdemo and capture both stdout and stderr, ignoring the crash + "${SCRIPT_DIR}/output/cprdemo" 2>&1 || true + echo "cprdemo test completed!" +fi + diff --git a/tests/test_libs/CMakeLists.txt b/tests/test_libs/CMakeLists.txt index 072e940..6b60e43 100644 --- a/tests/test_libs/CMakeLists.txt +++ b/tests/test_libs/CMakeLists.txt @@ -1,12 +1,29 @@ cmake_minimum_required(VERSION 3.16) -project(test_libs) +# Project setup +if(NOT DEFINED PROJECT_NAME) + message(FATAL_ERROR "PROJECT_NAME is not defined. Pass it via -DPROJECT_NAME=") +endif() + +string(TIMESTAMP PROJECT_VERSION "%Y.%m%d.%H%M") +project(${PROJECT_NAME} VERSION ${PROJECT_VERSION} LANGUAGES CXX) + +# Build configuration set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) - -# Force static linking -set(CMAKE_EXE_LINKER_FLAGS "-static -static-libgcc -static-libstdc++") +set(CMAKE_EXE_LINKER_FLAGS "-static") set(CMAKE_FIND_LIBRARY_SUFFIXES ".a") +set(BUILD_SHARED_LIBS OFF) +set(CMAKE_PREFIX_PATH /usr/local) + +# Create executable from the cpp file in root (special case for test_libs) +add_executable(${PROJECT_NAME} test_libs.cpp) + +# Pre-build script +add_custom_target(run_prebuild_script ALL + COMMAND bash ${CMAKE_CURRENT_SOURCE_DIR}/cmake_prebuild.sh + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) +add_dependencies(${PROJECT_NAME} run_prebuild_script) # Find packages find_package(PkgConfig REQUIRED) @@ -17,17 +34,8 @@ find_package(Threads REQUIRED) # Find SQLite3 using pkg-config as fallback pkg_check_modules(SQLite3 REQUIRED sqlite3) -# Add custom target to run cmake_prebuild.sh at the start of the build process -add_custom_target(run_prebuild_script ALL - COMMAND ${CMAKE_COMMAND} -E echo "Running cmake_prebuild.sh..." - COMMAND ${CMAKE_COMMAND} -E env bash ${CMAKE_CURRENT_SOURCE_DIR}/cmake_prebuild.sh - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} -) - - -add_executable(test_libs test_libs.cpp) - -target_link_libraries(test_libs +# Link libraries +target_link_libraries(${PROJECT_NAME} PRIVATE fmt::fmt spdlog::spdlog ${SQLite3_STATIC_LIBRARIES} @@ -35,5 +43,5 @@ target_link_libraries(test_libs rt ) -target_include_directories(test_libs PRIVATE ${SQLite3_INCLUDE_DIRS}) -target_compile_options(test_libs PRIVATE ${SQLite3_CFLAGS_OTHER}) \ No newline at end of file +target_include_directories(${PROJECT_NAME} PRIVATE ${SQLite3_INCLUDE_DIRS}) +target_compile_options(${PROJECT_NAME} PRIVATE ${SQLite3_CFLAGS_OTHER}) \ No newline at end of file