some infra

This commit is contained in:
2026-08-27 21:47:03 +02:00
parent 7c2a2aecd1
commit 73809eaa7c
61 changed files with 253 additions and 17 deletions
+1
View File
@@ -1,3 +1,4 @@
.env .env
build/ build/
.DS_Store .DS_Store
config.json
+69 -11
View File
@@ -388,6 +388,43 @@ else()
# which is before Concord has been built. # which is before Concord has been built.
file(MAKE_DIRECTORY "${CONCORD_INCLUDE_DIR}") file(MAKE_DIRECTORY "${CONCORD_INCLUDE_DIR}")
# Upstream's `make install` is three shell globs relative to the working
# directory, fed to install(1). When one matches nothing the shell passes it
# through literally and install reports `cannot stat 'include/*.h'`, which
# says nothing about which glob mattered or why. Do the copy ourselves with
# absolute paths and name the two failure modes that actually occur.
#
# The globs run at install time rather than configure time because
# gencodecs/discord_codecs.h does not exist until the build has generated it.
set(CONCORD_INSTALL_SCRIPT "${CMAKE_CURRENT_BINARY_DIR}/concord_install.cmake")
file(CONFIGURE
OUTPUT "${CONCORD_INSTALL_SCRIPT}"
@ONLY
CONTENT [[
file(MAKE_DIRECTORY "@CONCORD_INCLUDE_DIR@/concord" "@CONCORD_PREFIX@/lib")
# Upstream flattens all three header directories into one include/concord. Check
# them one at a time: a single empty directory is the interesting failure, and a
# combined glob would hide it behind whichever siblings still matched.
set(_hdrs "")
foreach(_dir include core gencodecs)
file(GLOB _found "@concord_SOURCE_DIR@/${_dir}/*.h")
if(NOT _found)
message(FATAL_ERROR
"Concord: no headers in @concord_SOURCE_DIR@/${_dir} -- the source "
"tree is incomplete; remove build/_deps and reconfigure to re-clone")
endif()
list(APPEND _hdrs ${_found})
endforeach()
file(COPY ${_hdrs} DESTINATION "@CONCORD_INCLUDE_DIR@/concord")
file(GLOB _libs "@concord_SOURCE_DIR@/lib/libdiscord.*")
if(NOT _libs)
message(FATAL_ERROR "Concord: build produced no library in @concord_SOURCE_DIR@/lib")
endif()
file(COPY ${_libs} DESTINATION "@CONCORD_PREFIX@/lib")
]])
# Concord gets our instrumentation but not our warning set: ASan only sees a # Concord gets our instrumentation but not our warning set: ASan only sees a
# bug if the translation unit that owns the memory was compiled with it, and # bug if the translation unit that owns the memory was compiled with it, and
# Concord allocates plenty that our code then touches. CFLAGS goes through # Concord allocates plenty that our code then touches. CFLAGS goes through
@@ -396,30 +433,43 @@ else()
# override those and break the build. # override those and break the build.
set(CONCORD_CFLAGS "-O2 -g $<JOIN:${HAMMY_INSTRUMENT_COMPILE}, >") set(CONCORD_CFLAGS "-O2 -g $<JOIN:${HAMMY_INSTRUMENT_COMPILE}, >")
# gencodecs/Makefile hardcodes CC/HOSTCC/CPP to "cc" for its host-side code
# generator, with plain '=' assignments that the environment cannot override.
# On a system where cc is GCC and we build with Clang, the generator is then
# handed our Clang-only flags (-fno-sanitize=function) and dies. Command-line
# assignments do beat makefile assignments, so force the compiler there --
# while leaving CFLAGS in the environment, per the note above.
#
# gencodecs-pp is a build-time text filter, run as `cpp ... | ./gencodecs-pp`.
# ASan's exit-time leak check would turn a leak in that throwaway tool into a
# build failure reported as a broken pipeline, so switch it off for the
# duration of Concord's build only; our own binary is unaffected.
ExternalProject_Add(concord_external ExternalProject_Add(concord_external
SOURCE_DIR "${concord_SOURCE_DIR}" SOURCE_DIR "${concord_SOURCE_DIR}"
DOWNLOAD_COMMAND "" # FetchContent already cloned it DOWNLOAD_COMMAND "" # FetchContent already cloned it
UPDATE_COMMAND "" UPDATE_COMMAND ""
CONFIGURE_COMMAND "" CONFIGURE_COMMAND ""
BUILD_IN_SOURCE TRUE # upstream's Makefile has no out-of-tree mode BUILD_IN_SOURCE TRUE # upstream's Makefile has no out-of-tree mode
BUILD_COMMAND ${CMAKE_COMMAND} -E env "CC=${CMAKE_C_COMPILER}" "CFLAGS=${CONCORD_CFLAGS}" # Upstream tracks nothing we can express as a byproduct, so the stamp
# would happily report "built" after a `make clean` emptied lib/.
BUILD_ALWAYS TRUE
BUILD_COMMAND ${CMAKE_COMMAND} -E env
"CC=${CMAKE_C_COMPILER}"
"CFLAGS=${CONCORD_CFLAGS}"
"ASAN_OPTIONS=detect_leaks=0"
${HAMMY_MAKE_EXECUTABLE} ${HAMMY_MAKE_EXECUTABLE}
INSTALL_COMMAND ${HAMMY_MAKE_EXECUTABLE} install "PREFIX=${CONCORD_PREFIX}" "CC=${CMAKE_C_COMPILER}"
"HOSTCC=${CMAKE_C_COMPILER}"
"CPP=${CMAKE_C_COMPILER} -E"
INSTALL_COMMAND ${CMAKE_COMMAND} -P "${CONCORD_INSTALL_SCRIPT}"
BUILD_BYPRODUCTS "${CONCORD_LIBRARY}" BUILD_BYPRODUCTS "${CONCORD_LIBRARY}"
USES_TERMINAL_BUILD TRUE # USES_TERMINAL_BUILD would silently disable LOG_BUILD; with
# LOG_OUTPUT_ON_FAILURE we get the full log exactly when it matters.
LOG_BUILD TRUE LOG_BUILD TRUE
LOG_INSTALL TRUE LOG_INSTALL TRUE
LOG_OUTPUT_ON_FAILURE TRUE LOG_OUTPUT_ON_FAILURE TRUE
) )
ExternalProject_Add_Step(concord_external mirror_headers
COMMAND ${CMAKE_COMMAND} -E copy_directory
"${CONCORD_INCLUDE_DIR}/concord"
"${PROJECT_SOURCE_DIR}/include/concord"
DEPENDEES install
COMMENT "Mirroring Concord headers into include/concord"
)
add_library(concord::concord STATIC IMPORTED GLOBAL) add_library(concord::concord STATIC IMPORTED GLOBAL)
set_target_properties(concord::concord PROPERTIES set_target_properties(concord::concord PROPERTIES
IMPORTED_LOCATION "${CONCORD_LIBRARY}" IMPORTED_LOCATION "${CONCORD_LIBRARY}"
@@ -456,6 +506,14 @@ target_include_directories(hammy PRIVATE
${PROJECT_SOURCE_DIR}/include ${PROJECT_SOURCE_DIR}/include
) )
target_include_directories(hammy SYSTEM PRIVATE
${PROJECT_SOURCE_DIR}/third_party
)
# Concord's headers arrive through concord::concord. CMake passes an imported
# target's interface includes as -isystem, so upstream's headers never trip our
# warning set and no second copy under third_party/ is needed.
target_link_libraries(hammy PRIVATE concord::concord) target_link_libraries(hammy PRIVATE concord::concord)
if(HAMMY_CONCORD_EXTERNAL) if(HAMMY_CONCORD_EXTERNAL)
add_dependencies(hammy ${HAMMY_CONCORD_EXTERNAL}) add_dependencies(hammy ${HAMMY_CONCORD_EXTERNAL})
+34
View File
@@ -0,0 +1,34 @@
#ifndef HAMMY_BOT_H
#define HAMMY_BOT_H
#include <concord/discord.h>
#include <dlibc/vector.h>
#include <stdlib.h>
#include <hammy/command.h>
typedef struct {
struct discord* client; // Owning reference to the client - handoff from main.c
bool commands_registered; // A flag if commands have been registered. Avoid re-registering every reconnect.
vector_t* commands; // vector_t of commands. Owning.
} hammy_bot_t;
// Alloc on heap and create
hammy_bot_t* hammy_bot_create();
// Sets the on_ready() call function pointer
bool hammy_bot_set_on_ready(hammy_bot_t* bot, void (*func)(struct discord* client, const struct discord_ready* event));
// Runs the bot. Return true on succeed, false on failure.
bool hammy_bot_run(hammy_bot_t* bot);
// Adds a command to the vector as a COPY, get rid of the old struct.
bool hammy_bot_add_command(hammy_bot_t* bot, hammy_command_t* command);
// Registers all commands. Return true if succeeded or already registered; false if failure.
bool hammy_bot_register_commands(hammy_bot_t* bot);
// Destroy bot, NULLs the passing reference
bool hammy_bot_destroy(hammy_bot_t** bot);
#endif
+17
View File
@@ -0,0 +1,17 @@
#ifndef HAMMY_COMMAND_H
#define HAMMY_COMMAND_H
#include <stdlib.h>
typedef struct {
// TODO: add shit here
char empty;
} hammy_command_t;
// Creates the command on the heap, returns NULL if failed.
hammy_command_t* hammy_command_create();
// Destroys a command.
void hammy_command_destroy(void* cmd);
#endif
+80
View File
@@ -0,0 +1,80 @@
#include <hammy/bot.h>
hammy_bot_t* hammy_bot_create() {
hammy_bot_t* bot = (hammy_bot_t*)malloc(sizeof(hammy_bot_t));
if (!bot) {
return NULL;
}
// Set defaults
bot->client = NULL;
bot->commands_registered = false;
bot->commands = vector_create(sizeof(hammy_command_t));
// Check if vector allocation errored
if (!bot->commands) {
free(bot);
return NULL;
}
if (vector_set_destructor(bot->commands, &hammy_command_destroy) < 0) {
vector_destroy(&bot->commands);
free(bot);
return NULL;
}
// Init the discord client - even though this is an owning reference, the main() and others may call functions on it (if we don't stay singlethread anymore then FIX THIS)
bot->client = discord_from_json("config.json");
if (!bot->client) {
vector_destroy(&bot->commands);
free(bot);
return NULL;
}
return bot;
}
bool hammy_bot_set_on_ready(hammy_bot_t* bot, void (*func)(struct discord* client, const struct discord_ready* event)) {
if (!bot || !func || !bot->client) { return false; }
discord_set_on_ready(bot->client, func);
return true;
}
bool hammy_bot_run(hammy_bot_t* bot) {
if (!bot || !bot->client) { return false; }
discord_run(bot->client);
return true;
}
bool hammy_bot_add_command(hammy_bot_t* bot, hammy_command_t* command) {
if (!bot || !command) { return false; }
// Push into commands vector
if (vector_push_back(bot->commands, command) < 0) {
return false;
}
return true;
}
bool hammy_bot_register_commands(hammy_bot_t* bot) {
if (!bot) { return false; }
// TODO: Figure out registration logic
}
bool hammy_bot_destroy(hammy_bot_t** bot) {
if (!bot || !(*bot)) { return false; }
if ((*bot)->client) {
discord_cleanup((*bot)->client);
}
// TODO: Potential other stuff here
vector_destroy(&(*bot)->commands); // commands is NULL form hereon, calls destructors on commands automatically
free(*bot);
*bot = NULL;
}
+24
View File
@@ -0,0 +1,24 @@
#include <hammy/command.h>
hammy_command_t* hammy_command_create() {
hammy_command_t* cmd = (hammy_command_t*)malloc(sizeof(hammy_command_t));
if (!cmd) {
return NULL;
}
// TODO: set defaults
return cmd;
}
void hammy_command_destroy(void* cmd) {
if (!cmd) {
return;
}
cmd = (hammy_command_t*)cmd;
// TODO: Other things here eventually
free(cmd);
}
+28 -6
View File
@@ -1,21 +1,43 @@
#include <string.h> #include <string.h>
#include <concord/discord.h> #include <signal.h>
#include <concord/log.h> #include <concord/log.h>
#include <hammy/bot.h>
static void on_signal(int sig) {
(void)sig;
discord_shutdown_all();
}
void on_ready(struct discord* client, const struct discord_ready* event) { void on_ready(struct discord* client, const struct discord_ready* event) {
(void)client;
log_info("Logged in as %s", event->user->username); log_info("Logged in as %s", event->user->username);
} }
int main(void) { int main(void) {
struct sigaction sa = { 0 };
sa.sa_handler = &on_signal;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0; /* deliberately no SA_RESTART */
sigaction(SIGTERM, &sa, NULL);
sigaction(SIGINT, &sa, NULL);
ccord_global_init(); ccord_global_init();
struct discord* client = discord_config_init("config.json"); hammy_bot_t* bot = hammy_bot_create();
if (!bot) {
log_error("Hammy Bot creation returned NULL! Bailing!");
ccord_global_cleanup();
discord_set_on_ready(client, &on_ready); return 1;
}
hammy_bot_set_on_ready(bot, &on_ready);
hammy_bot_run(bot); // This will block and exit afterwards
hammy_bot_destroy(&bot);
discord_run(client);
discord_cleanup(client);
ccord_global_cleanup(); ccord_global_cleanup();
return 0; return 0;