From 10e26e8a6c084e431dc3e9755fa0ca7cca73ead4 Mon Sep 17 00:00:00 2001 From: j842 Date: Mon, 9 Jun 2025 17:49:36 +1200 Subject: [PATCH] works --- CLAUDE.md | 117 ++++++++++++++++++++++++++++++++++++++++++ ipdemo/CMakeLists.txt | 23 +++++++++ ipdemo/src/main.cpp | 45 ++++++++++------ 3 files changed, 170 insertions(+), 15 deletions(-) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..42273e9 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,117 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +This repository contains a Docker-based build system for creating statically-linked C++ executables using Alpine Linux and musl libc. It consists of: + +1. **dropshell-build-base**: A comprehensive C++ development base image with Drogon web framework and database support +2. **dropshell-build**: A multi-stage Docker build system that uses the base image to compile C++ projects +3. **ipdemo**: A sample application demonstrating the build system + +## Key Commands + +### Building the Base Image +```bash +cd build-base +./build.sh +``` +This creates the `gitea.jde.nz/public/dropshell-build-base:latest` Docker image with all dependencies. + +### Building a Project +```bash +./build.sh +``` +This builds the project defined in `build.sh` (default: ipdemo) and outputs the binary to `output/`. + +### Testing the Built Binary +```bash +./test.sh +``` +Runs the compiled binary from `output/` directory. + +### Environment Variables +- `CMAKE_BUILD_TYPE`: Set to "Debug" or "Release" (default: Debug) +- `PROJECT`: The project name to build (default: ipdemo) + +## Architecture and Design + +### Build System Structure + +The build system uses a two-stage approach: + +1. **Base Image** (`dropshell-build-base`): Contains all dependencies compiled from source with static linking: + - C++ compiler toolchain with ccache and mold linker + - Libraries: OpenSSL, jsoncpp, PostgreSQL, MariaDB, MySQL, SQLite3, cURL, c-ares + - Drogon web framework with full database support + - All libraries built with `-fPIC` for static linking + +2. **Project Build** (`Dockerfile.dropshell-build`): Multi-stage build that: + - Uses build cache mounts for faster rebuilds + - Runs cmake_prebuild.sh for custom pre-build steps + - Generates version information dynamically + - Produces a single static binary in a scratch container + +### CMake Configuration + +The CMakeLists.txt enforces static linking through: +- `CMAKE_EXE_LINKER_FLAGS` with `-static` flag +- `CMAKE_FIND_LIBRARY_SUFFIXES` set to `.a` +- `BUILD_SHARED_LIBS` forced to OFF +- Custom library paths from the base image + +Important: Projects must set CMAKE_PREFIX_PATH and explicitly link PostgreSQL libraries: +```cmake +# Set paths for libraries before finding Drogon +set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} + /usr/local/jsoncpp + /usr/local/openssl-musl + /usr/local/pgsql + # ... other library paths +) + +# Additional PostgreSQL libraries needed for static linking +set(POSTGRESQL_EXTRA_LIBS + /usr/local/pgsql/lib/libpgcommon.a + /usr/local/pgsql/lib/libpgport.a +) +``` + +### Project Structure for New Applications + +New C++ projects should follow this structure: +``` +project-name/ +├── CMakeLists.txt # Based on ipdemo example +├── cmake_prebuild.sh # Optional pre-build script +└── src/ + ├── main.cpp # Application entry point + └── version.hpp.in # Version template +``` + +### Adding New Projects + +To build a different project: +1. Create a directory with the project structure above +2. Modify `build.sh` to set `PROJECT="your-project-name"` +3. Run `./build.sh` to build + +### Static Linking Details + +The system ensures complete static linking by: +- Using Alpine Linux's musl libc +- Building all dependencies with `--enable-static --disable-shared` +- Setting `-fPIC` for position-independent code +- Using mold linker with `-static` flag +- Configuring CMake to prefer `.a` libraries + +### Database Support + +Applications can use: +- PostgreSQL via libpq at `/usr/local/pgsql/` +- MySQL via client library at `/usr/local/mysql/` +- MariaDB via connector at `/usr/local/mariadb-connector-c/` +- SQLite3 at `/usr/local/sqlite3/` + +All database libraries are statically linked into the final binary. \ No newline at end of file diff --git a/ipdemo/CMakeLists.txt b/ipdemo/CMakeLists.txt index 2a6d52d..3b36690 100644 --- a/ipdemo/CMakeLists.txt +++ b/ipdemo/CMakeLists.txt @@ -96,6 +96,22 @@ set(EXTRA_LIBS ) +# Set paths for libraries before finding Drogon +set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} + /usr/local/jsoncpp + /usr/local/openssl-musl + /usr/local/pgsql + /usr/local/mariadb-connector-c + /usr/local/sqlite3 + /usr/local/mysql + /usr/local/cares + /usr/local/curl +) + +# Explicitly set jsoncpp paths for Drogon's FindJsoncpp.cmake +set(JSONCPP_INCLUDE_DIRS /usr/local/jsoncpp/include) +set(JSONCPP_LIBRARIES /usr/local/jsoncpp/lib/libjsoncpp.a) + ########## # If you include the drogon source code locally in your project, use this method to add drogon # add_subdirectory(external/drogon) @@ -105,10 +121,17 @@ find_package(Drogon CONFIG REQUIRED) target_link_libraries(${PROJECT_NAME} PRIVATE Drogon::Drogon) +# Additional PostgreSQL libraries needed for static linking +set(POSTGRESQL_EXTRA_LIBS + /usr/local/pgsql/lib/libpgcommon.a + /usr/local/pgsql/lib/libpgport.a +) + # Link libraries target_link_libraries(${PROJECT_EXE_NAME} PRIVATE nlohmann_json::nlohmann_json Drogon::Drogon + ${POSTGRESQL_EXTRA_LIBS} ${EXTRA_LIBS} ) diff --git a/ipdemo/src/main.cpp b/ipdemo/src/main.cpp index 3d8ed7d..5962106 100644 --- a/ipdemo/src/main.cpp +++ b/ipdemo/src/main.cpp @@ -1,7 +1,10 @@ #include +#include +#include +#include #include -#include +#include #include "version.hpp" #include "assert.hpp" @@ -14,24 +17,36 @@ int main() { std::cout << std::endl; std::cout << "Retrieving IP address..." << std::endl; - auto loop = trantor::EventLoop::getEventLoopOfCurrentThread(); - auto client = drogon::HttpClient::newHttpClient("http://ipinfo.io", loop); - auto req = drogon::HttpRequest::newHttpRequest(); - req->setMethod(drogon::Get); - req->setPath("/ip"); + // Initialize drogon app but don't run it + drogon::app().setLogLevel(trantor::Logger::kError); + std::string ip_body; int status = 0; bool done = false; - client->sendRequest(req, [&](drogon::ReqResult result, const drogon::HttpResponsePtr &resp) { - if (result == drogon::ReqResult::Ok && resp) { - ip_body = resp->body(); - status = resp->statusCode(); - } else { - status = 0; - } - done = true; + + // Run in a separate thread to avoid event loop conflicts + std::thread worker([&]() { + trantor::EventLoop loop; + auto client = drogon::HttpClient::newHttpClient("http://ipinfo.io", &loop); + auto req = drogon::HttpRequest::newHttpRequest(); + req->setMethod(drogon::Get); + req->setPath("/ip"); + + client->sendRequest(req, [&](drogon::ReqResult result, const drogon::HttpResponsePtr &resp) { + if (result == drogon::ReqResult::Ok && resp) { + ip_body = resp->body(); + status = resp->statusCode(); + } else { + status = 0; + } + done = true; + loop.quit(); + }); + + loop.loop(); }); - while (!done) loop->loopOnce(); + + worker.join(); ASSERT(status == 200, "Failed to get IP");