From 1ba702048bf863d93a24d091a03ae01ec2000349 Mon Sep 17 00:00:00 2001 From: James Orson Date: Mon, 30 Dec 2024 14:06:33 -0800 Subject: [PATCH] Initial commit --- .github/workflows/ci.yml | 59 +++++++++++++++++++ .github/workflows/lint.yml | 25 ++++++++ .github/workflows/mirror.yml | 15 +++++ .gitignore | 37 ++++++++++++ .gitmodules | 4 ++ .vscode/c_cpp_properties.json | 21 +++++++ .vscode/extensions.json | 8 +++ CMakeLists.txt | 19 ++++++ Makefile | 106 ++++++++++++++++++++++++++++++++++ README.md | 1 + compile_commands.json | 1 + docs/.gitkeep | 0 include/exo/event.hpp | 21 +++++++ include/exo/store.hpp | 40 +++++++++++++ include/exo/stream.hpp | 45 +++++++++++++++ scripts/setup | 37 ++++++++++++ src/CMakeLists.txt | 10 ++++ src/exo/CMakeLists.txt | 11 ++++ src/exo/store.cpp | 1 + src/main.cpp | 10 ++++ test/CMakeLists.txt | 40 +++++++++++++ test/lib/.gitkeep | 0 test/lib/catch2 | 1 + test/test_event.cpp | 11 ++++ test/test_store.cpp | 26 +++++++++ test/test_stream.cpp | 59 +++++++++++++++++++ 26 files changed, 608 insertions(+) create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/lint.yml create mode 100644 .github/workflows/mirror.yml create mode 100644 .gitignore create mode 100644 .gitmodules create mode 100644 .vscode/c_cpp_properties.json create mode 100644 .vscode/extensions.json create mode 100644 CMakeLists.txt create mode 100644 Makefile create mode 100644 README.md create mode 120000 compile_commands.json create mode 100644 docs/.gitkeep create mode 100644 include/exo/event.hpp create mode 100644 include/exo/store.hpp create mode 100644 include/exo/stream.hpp create mode 100755 scripts/setup create mode 100644 src/CMakeLists.txt create mode 100644 src/exo/CMakeLists.txt create mode 100644 src/exo/store.cpp create mode 100644 src/main.cpp create mode 100644 test/CMakeLists.txt create mode 100644 test/lib/.gitkeep create mode 160000 test/lib/catch2 create mode 100644 test/test_event.cpp create mode 100644 test/test_store.cpp create mode 100644 test/test_stream.cpp diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..3c2950a --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,59 @@ +name: CI build + +on: + push: + branches: + - main + pull_request: + branches: + - main + workflow_dispatch: + +jobs: + build: + runs-on: ${{ matrix.os }} + + strategy: + fail-fast: false + matrix: + os: + - ubuntu-latest + - macos-latest + build_type: + - Release + compiler: + - gcc + - clang + include: + - os: macos-latest + compiler: clang + cpp_compiler: clang++ + - os: ubuntu-latest + compiler: gcc + cpp_compiler: g++ + - os: ubuntu-latest + compiler: clang + cpp_compiler: clang++ + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 1 + submodules: true + + - name: Setup + run: | + echo ${HOME}/.local/bin >> ${GITHUB_PATH} + make setup + + - name: Configure + run: make configure + + - name: Build + run: make build + + - name: Test + run: make test + + - name: Run + run: make run diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000..9c763ca --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,25 @@ +name: Lint +on: + pull_request: + workflow_dispatch: + +jobs: + lint: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 1 + submodules: true + + - name: Setup + run: | + echo ${HOME}/.local/bin >> ${GITHUB_PATH} + make setup + + - name: Configure + run: make configure + + - name: Lint + run: make lint diff --git a/.github/workflows/mirror.yml b/.github/workflows/mirror.yml new file mode 100644 index 0000000..e810799 --- /dev/null +++ b/.github/workflows/mirror.yml @@ -0,0 +1,15 @@ +name: Mirror +on: + push: + branches: main + workflow_dispatch: + +jobs: + mirror: + runs-on: ubuntu-latest + + steps: + - uses: jamesaorson/composite-git-mirror@main + with: + target-git-url: git@git.sr.ht:~exokomodo/${{ github.event.repository.name }} + ssh-private-key: ${{ secrets.SRHT_SSH_PRIVATE_KEY }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3828c44 --- /dev/null +++ b/.gitignore @@ -0,0 +1,37 @@ +# Compiled Object files +**/.DS_Store +*.slo +*.lo +*.o +*.obj + +# Precompiled Headers +*.gch +*.pch + +# Compiled Dynamic libraries +*.so +*.dylib +*.dll + +# Fortran module files +*.mod +*.smod + +# Compiled Static libraries +*.lai +*.la +*.a +*.lib + +# Executables +*.exe +*.out +*.app + +# Editors +.idea/*.xml + +# Build output +build/* +!**/.gitkeep diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..66b3cf7 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,4 @@ +[submodule "test/lib/catch2"] + path = test/lib/catch2 + url = https://github.com/catchorg/Catch2.git + branch = devel diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json new file mode 100644 index 0000000..279400c --- /dev/null +++ b/.vscode/c_cpp_properties.json @@ -0,0 +1,21 @@ +{ + "configurations": [ + { + "name": "default", + "includePath": [ + "${workspaceFolder}/include", + "${workspaceFolder}/test/lib/catch2/src", + "${workspaceFolder}/build/test/lib/catch2/generated-includes" + ], + "macFrameworkPath": [ + "/System/Library/Frameworks", + "/Library/Frameworks" + ], + "intelliSenseMode": "macos-clang-x64", + "compilerPath": "/usr/bin/clang", + "cStandard": "c17", + "cppStandard": "c++17" + } + ], + "version": 4 +} \ No newline at end of file diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..106428c --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,8 @@ +{ + "recommendations": [ + "ms-vscode.cpptools-extension-pack", + "ms-vscode.cmake-tools", + "twxs.cmake", + "ms-vscode.makefile-tools" + ] +} \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..f3e93e3 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 3.31) + +project(exosourcing + LANGUAGES CXX C) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) +set(CMAKE_INSTALL_PREFIX ${PROJECT_SOURCE_DIR}) + +set(INCLUDE_DIR_exo ${PROJECT_SOURCE_DIR}/include) + +add_subdirectory(src) + +# Don't even look at tests if we're not top level +if(NOT PROJECT_IS_TOP_LEVEL) + return() +endif() +include(CTest) +add_subdirectory(test) diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..8bf5f91 --- /dev/null +++ b/Makefile @@ -0,0 +1,106 @@ +SHELL := /bin/bash +.SHELLFLAGS = -e -c +.DEFAULT_GOAL := help +.ONESHELL: + +UNAME_S := $(shell uname -s) + +NO_GENERATE_TEMPLATES ?= 0 +RELEASE_KIND ?= debug + +ifeq ($(UNAME_S),Linux) +CC := gcc +endif +ifeq ($(UNAME_S),Darwin) +CC := clang +endif + +export PATH := "$(shell pwd)/bin:$(PATH)" + +BUILD_DIR := ./build +BINARY := $(shell pwd)/build/src/demo +TEST_BINARY := $(shell pwd)/build/test/tests + +.PHONY: configure +configure: ## Configure default candidate + cmake -B$(BUILD_DIR) -S. + +.PHONY: configure/debug +configure/debug: ## Configure debug candidate + $(MAKE) configure RELEASE_KIND=debug + +.PHONY: configure/release +configure/release: ## Configure release candidate + $(MAKE) configure RELEASE_KIND=release + +.PHONY: build +build: configure ## Build default candidate + cmake --build $(BUILD_DIR) + +.PHONY: build/debug +build/debug: ## Build debug candidate + $(MAKE) build RELEASE_KIND=debug + +.PHONY: build/release +build/release: ## Build release candidate + $(MAKE) build RELEASE_KIND=release + +.PHONY: test +test: $(TEST_BINARY) ## Run test binary + $< + +.PHONY: run +run: $(BINARY) ## Run binary + $< + +.PHONY: run/debug +run/debug: ## Run debug candidate + $(MAKE) build RELEASE_KIND=debug + +.PHONY: run/release +run/release: ## Run release candidate + $(MAKE) build RELEASE_KIND=release + +.PHONY: setup +setup: ./scripts/setup ## Setup dependencies for system + @$< + +SOURCE_FILES := $(shell find ./src -type f -name '*.cpp') +HEADER_FILES := $(shell find ./src -type f -name '*.hpp') + +.PHONY: format +format: ## Format the C/C++ code + @echo $(SOURCE_FILES) $(HEADER_FILES) | xargs clang-format -i + +.PHONY: lint +lint: ## Lint the C/C++ code + @BAD_FILES=$(shell mktemp) + echo "[clang-format] BEGIN" + echo $(SOURCE_FILES) $(HEADER_FILES) | xargs -I {} $(SHELL) -c 'clang-format --dry-run --Werror {} || echo {}' >> $${BAD_FILES} + if [[ -s $${BAD_FILES} ]]; then + echo "[clang-format] Found formatting errors" + cat $${BAD_FILES} + else + echo "[clang-format] No formatting errors" + fi + echo "[clang-format] END" + echo "[clang-tidy] BEGIN" + $(MAKE) tidy + echo "[clang-tidy] END" + if [[ -s $${BAD_FILES} ]]; then + exit 1 + fi + +.PHONY: tidy +tidy: ## Tidy the C/C++ code + @clang-tidy $(SOURCE_FILES) $(HEADER_FILES) + +.PHONY: version +version: ## Version info + $(MAKE) --version + $(CC) --version + $(MAKE) run/version + +.PHONY: help +help: ## Displays help info + @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n"} /^[a-zA-Z_-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) diff --git a/README.md b/README.md new file mode 100644 index 0000000..ee0626d --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# Exosourcing diff --git a/compile_commands.json b/compile_commands.json new file mode 120000 index 0000000..affbd32 --- /dev/null +++ b/compile_commands.json @@ -0,0 +1 @@ +./build/compile_commands.json \ No newline at end of file diff --git a/docs/.gitkeep b/docs/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/include/exo/event.hpp b/include/exo/event.hpp new file mode 100644 index 0000000..9e4c974 --- /dev/null +++ b/include/exo/event.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include + +namespace exo { + template + struct Event { + T data; + + bool operator==(const Event& other) const { + return this->data == other.data; + } + }; + + template + Event make_event(T data) { + return { + .data = data, + }; + } +} diff --git a/include/exo/store.hpp b/include/exo/store.hpp new file mode 100644 index 0000000..a879b0f --- /dev/null +++ b/include/exo/store.hpp @@ -0,0 +1,40 @@ +#pragma once + +#include + +#include + +namespace exo { + // NOTE: Forward declare all friend types + template + struct MemoryStoreInspector; + + template + struct Store { + bool emplace_back(const exo::Event event); + }; + + template + struct MemoryStore : exo::Store { + friend struct exo::MemoryStoreInspector; + + bool emplace_back(const exo::Event event) { + try { + this->events.emplace_back(event); + } catch (const std::exception& e) { + return false; + } + return true; + } + + protected: + // NOTE: Using a std::vector, since we always `emplace_back`, + // allowing for guaranteed O(1) insertion + std::vector> events; + }; + + template + exo::MemoryStore make_memory_store() { + return {}; + } +} diff --git a/include/exo/stream.hpp b/include/exo/stream.hpp new file mode 100644 index 0000000..60c7817 --- /dev/null +++ b/include/exo/stream.hpp @@ -0,0 +1,45 @@ +#pragma once + +#include + +#include +#include + +namespace exo { + template + struct MemoryStoreInspector { + static std::vector> get_events(const exo::MemoryStore& store) { + std::vector> copy = store.events; + return copy; + } + }; + + template + struct TypedStream { + std::vector> get_all() const; + + protected: + S store = {}; + }; + + template + struct TypedStream> { + explicit TypedStream(exo::MemoryStore& store) : store(store) {} + + std::vector> get_all() const { + return exo::MemoryStoreInspector::get_events(this->store); + } + + protected: + // NOTE: May be fine to stick with reference if we can prove ownership + exo::MemoryStore& store; + }; + + template + exo::TypedStream make_typed_stream(S& store); + + template + exo::TypedStream> make_typed_stream(exo::MemoryStore& store) { + return TypedStream>(store); + } +} diff --git a/scripts/setup b/scripts/setup new file mode 100755 index 0000000..500bed2 --- /dev/null +++ b/scripts/setup @@ -0,0 +1,37 @@ +#! /bin/bash + +set -euo pipefail + +cd $(dirname ${BASH_SOURCE[0]})/.. + +UNAME_S=$(uname -s) + +case ${UNAME_S} in + Linux*) + if sudo -v; then + if command -v apt-get >> /dev/null; then + sudo apt-get update + sudo apt-get install \ + -y \ + clang-format \ + clang-tidy \ + cmake \ + ninja-build + fi + fi + ;; + Darwin*) + brew install \ + cmake \ + llvm \ + ninja + BIN_DIR=${HOME}/.local/bin + mkdir -p ${BIN_DIR} + ln -s "$(brew --prefix llvm)/bin/clang-format" ${BIN_DIR}/clang-format + ln -s "$(brew --prefix llvm)/bin/clang-tidy" ${BIN_DIR}/clang-tidy + ;; + *) + echo "Unsupported platform: ${UNAME_S}" + exit 1 + ;; +esac diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..b93afb6 --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,10 @@ +cmake_minimum_required(VERSION 3.31) +project(exo) + +set(SOURCE_FILES main.cpp) + +add_subdirectory(exo) + +add_executable(demo ${SOURCE_FILES}) +target_include_directories(demo PUBLIC include) +target_link_libraries(demo exo) diff --git a/src/exo/CMakeLists.txt b/src/exo/CMakeLists.txt new file mode 100644 index 0000000..a277e19 --- /dev/null +++ b/src/exo/CMakeLists.txt @@ -0,0 +1,11 @@ +cmake_minimum_required(VERSION 3.31) +project(exo C CXX) + +set(SOURCE_FILES + store.cpp +) + +add_library(exo SHARED STATIC ${SOURCE_FILES}) +target_include_directories( + exo + PUBLIC ${INCLUDE_DIR_exo}) diff --git a/src/exo/store.cpp b/src/exo/store.cpp new file mode 100644 index 0000000..d9d7a1b --- /dev/null +++ b/src/exo/store.cpp @@ -0,0 +1 @@ +#include diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..9d01948 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,10 @@ +#include + +#include +#include +#include + +int main(int argc, const char *argv[]) { + std::cout << "Hello from Exosourcing" << std::endl; + return 0; +} diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt new file mode 100644 index 0000000..9282d48 --- /dev/null +++ b/test/CMakeLists.txt @@ -0,0 +1,40 @@ +cmake_minimum_required(VERSION 3.31) +project(tests) + +set(SOURCE_FILES + test_event.cpp + test_store.cpp + test_stream.cpp +) +set(SOURCE_DIR_catch2 lib/catch2) +set(INCLUDE_DIR_catch2 ${SOURCE_DIR_catch2}/src) + +include_directories(${INCLUDE_DIR_exo}) +include_directories(${INCLUDE_DIR_catch2}) + +# Testing +enable_testing() + +add_test(NAME cli.has_help COMMAND tests --help) + +add_subdirectory(${SOURCE_DIR_catch2}) +include(${SOURCE_DIR_catch2}/extras/Catch.cmake) +add_executable(tests ${SOURCE_FILES}) +target_link_libraries( + tests + PUBLIC exo + PRIVATE Catch2::Catch2WithMain) +catch_discover_tests( + tests + TEST_PREFIX + "unittests." + REPORTER + XML + OUTPUT_DIR + . + OUTPUT_PREFIX + "unittests." + OUTPUT_SUFFIX + .xml) + +install(TARGETS tests DESTINATION bin) diff --git a/test/lib/.gitkeep b/test/lib/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/test/lib/catch2 b/test/lib/catch2 new file mode 160000 index 0000000..0321d2f --- /dev/null +++ b/test/lib/catch2 @@ -0,0 +1 @@ +Subproject commit 0321d2fce328b5e2ad106a8230ff20e0d5bf5501 diff --git a/test/test_event.cpp b/test/test_event.cpp new file mode 100644 index 0000000..12ec4f4 --- /dev/null +++ b/test/test_event.cpp @@ -0,0 +1,11 @@ +#include + +#include + +#define PREFIX "[exo::Event]" + +TEST_CASE("Can create event", PREFIX) +{ + const exo::Event event = {.data = 1}; + REQUIRE(event.data == 1); +} diff --git a/test/test_store.cpp b/test/test_store.cpp new file mode 100644 index 0000000..aa08dcd --- /dev/null +++ b/test/test_store.cpp @@ -0,0 +1,26 @@ +#include + +#include +#include +#include + +#define PREFIX "[exo::Store]" + +TEST_CASE("Can create memory store", "[exo::MemoryStore]") +{ + const exo::MemoryStore store; +} + +TEST_CASE("Can insert events into a memory store", "[exo::MemoryStore]") +{ + exo::MemoryStore store; + REQUIRE(store.emplace_back(exo::make_event(1))); + REQUIRE(store.emplace_back(exo::make_event(2))); + + const auto events = exo::MemoryStoreInspector::get_events(store); + REQUIRE(exo::make_event(1) == events.at(0)); + REQUIRE(exo::make_event(2) == events.at(1)); + REQUIRE_FALSE(exo::make_event(1) == events.at(1)); + REQUIRE_FALSE(exo::make_event(2) == events.at(0)); +} + diff --git a/test/test_stream.cpp b/test/test_stream.cpp new file mode 100644 index 0000000..1ddfdeb --- /dev/null +++ b/test/test_stream.cpp @@ -0,0 +1,59 @@ +#include + +#include + +#include +#include +#include + +#define PREFIX "[exo::Stream]" + +TEST_CASE("Can read events from a stream", PREFIX) +{ + exo::MemoryStore store; + REQUIRE(store.emplace_back(exo::make_event(1))); + REQUIRE(store.emplace_back(exo::make_event(2))); + + auto stream = exo::make_typed_stream(store); + auto events = stream.get_all(); + REQUIRE(exo::make_event(1) == events.at(0)); + REQUIRE(exo::make_event(2) == events.at(1)); + + REQUIRE_FALSE(exo::make_event(1) == events.at(1)); + REQUIRE_FALSE(exo::make_event(2) == events.at(0)); +} + +TEST_CASE("Can read new events from a stream", PREFIX) +{ + exo::MemoryStore store; + REQUIRE(store.emplace_back(exo::make_event(1))); + REQUIRE(store.emplace_back(exo::make_event(2))); + + const auto stream = exo::make_typed_stream(store); + { + const auto events = stream.get_all(); + REQUIRE(events.size() == 2); + REQUIRE(exo::make_event(1) == events.at(0)); + REQUIRE(exo::make_event(2) == events.at(1)); + + REQUIRE_FALSE(exo::make_event(1) == events.at(1)); + REQUIRE_FALSE(exo::make_event(2) == events.at(0)); + } + { + const auto events = stream.get_all(); + REQUIRE(events.size() == 2); + REQUIRE(store.emplace_back(exo::make_event(3))); + } + { + const auto events = stream.get_all(); + REQUIRE(events.size() == 3); + + REQUIRE(exo::make_event(1) == events.at(0)); + REQUIRE(exo::make_event(2) == events.at(1)); + REQUIRE(exo::make_event(3) == events.at(2)); + + REQUIRE_FALSE(exo::make_event(1) == events.at(1)); + REQUIRE_FALSE(exo::make_event(2) == events.at(2)); + REQUIRE_FALSE(exo::make_event(3) == events.at(0)); + } +}