diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b67810c --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +/.vscode/ +/bin/ +/out/ +makevars.mk diff --git a/kmake.code-workspace b/kmake.code-workspace new file mode 100644 index 0000000..7ff0a6a --- /dev/null +++ b/kmake.code-workspace @@ -0,0 +1,15 @@ +{ + "folders": [ + { + "path": "." + } + ], + "settings": { + // Friendly reminder to alter the includepath + // in YOUR OWN .vscode/settings.json + "C_Cpp.default.includePath": [ + "./src" + ], + "terminal.integrated.defaultProfile.windows": "MSYS2 UCRT", + } +} \ No newline at end of file diff --git a/kmake.yml b/kmake.yml new file mode 100644 index 0000000..ba0b559 --- /dev/null +++ b/kmake.yml @@ -0,0 +1,51 @@ +name: '' +author: '' # author and authors are added together +authors: [''] +descr: + - '' +# Amount of text to output +verbosity: normal +build: + # Set build folders + src: + path: './src' + targets: ['c','cpp'] # Set targets for compilation + out: + path: './out' + target: 'exe' + bin: + path: './bin' + target: 'o' + deps: + path: './bin' # Reuse binaries folder (default) + target: 'd' + rev_target: 'rd' # Extension for reverse dependencies + style: gcc # Dependency style of generated output + + # Set number of threads to use + # for compilation of multiple files + # 0 To turn off multithreading. + # Default: 0 + # Recommended: 4 + threads: 4 +vars: +distros: + distro_name: + # Distro Vars (overrides global) + vars: + # Commands executed at the start + start: + - '' + # Commands to generate dependencies + deps: + - '' + # Commands to compile one file + # Files may be compiled on multiple threads + compile: + - '' + # Commands to link one file + link: + - '' + # Commands executed after linking program + end: + - '' diff --git a/makefile b/makefile new file mode 100644 index 0000000..c480ac5 --- /dev/null +++ b/makefile @@ -0,0 +1,78 @@ +# Include variables from user defined +# makevars.mak for paths & other stuff. +include makevars.mk + +#Compiler and Linker +CC := g++ + +#The Target Binary Program +TARGET := out.exe + +#The Directories, Source, Includes, Objects, Binary and Resources +SRCDIR := src +BUILDDIR := bin +TARGETDIR := out +SRCEXT := cpp +DEPEXT := d +OBJEXT := o + +#Flags, Libraries and Includes +ROOT := ./ +CFLAGS := -std=c++20 -O2 \ + -Wall -Werror -Wextra \ + -Wshadow -Wnon-virtual-dtor -Wold-style-cast -Wcast-align \ + -Wunused -Woverloaded-virtual -Wconversion \ + -Wsign-conversion -Wnull-dereference -Wdouble-promotion \ + -Wformat=2 -Wimplicit-fallthrough -Wsuggest-override \ + -Wextra-semi -Wduplicated-cond -Wduplicated-branches \ + -Wlogical-op -Wuseless-cast -Wno-unused-parameter +LFLAGS := -std=c++20 -static-libstdc++ -static-libgcc \ + -Wl,--fatal-warnings -Wl,--warn-common +LIBDIRS := $(EXT_LIBDIRS) +LIB := $(EXT_LIB) +INC := -I./src/ $(EXT_INC) + +#--------------------------------------------------------------------------------- +# DO NOT EDIT BELOW THIS LINE +#--------------------------------------------------------------------------------- +SOURCES := $(shell find $(SRCDIR) -type f -name *.$(SRCEXT)) +OBJECTS := $(patsubst $(SRCDIR)/%,$(BUILDDIR)/%,$(SOURCES:.$(SRCEXT)=.$(OBJEXT))) + +#Defauilt Make +all: directories $(TARGET) + +#Remake +remake: cleaner all + +#Make the Directories +directories: + @mkdir -p $(TARGETDIR) + @mkdir -p $(BUILDDIR) + +#Clean only Objecst +clean: + @$(RM) -rf $(BUILDDIR) + +#Full Clean, Objects and Binaries +cleaner: clean + @$(RM) -rf $(TARGETDIR) + +#Pull in dependency info for *existing* .o files +-include $(OBJECTS:.$(OBJEXT)=.$(DEPEXT)) + +#Link +$(TARGET): $(OBJECTS) + $(CC) $(LFLAGS) -o $(TARGETDIR)/$(TARGET) $^ $(LIBDIRS) $(LIB) + +#Compile +$(BUILDDIR)/%.$(OBJEXT): $(SRCDIR)/%.$(SRCEXT) + @mkdir -p $(dir $@) + $(CC) $(CFLAGS) $(INC) -c -o $@ $< + @$(CC) $(CFLAGS) -MM $(SRCDIR)/$*.$(SRCEXT) > $(BUILDDIR)/$*.$(DEPEXT) + @cp -f $(BUILDDIR)/$*.$(DEPEXT) $(BUILDDIR)/$*.$(DEPEXT).tmp + @sed -e 's|.*:|$(BUILDDIR)/$*.$(OBJEXT):|' < $(BUILDDIR)/$*.$(DEPEXT).tmp > $(BUILDDIR)/$*.$(DEPEXT) + @sed -e 's/.*://' -e 's/\\$$//' < $(BUILDDIR)/$*.$(DEPEXT).tmp | fmt -1 | sed -e 's/^ *//' -e 's/$$/:/' >> $(BUILDDIR)/$*.$(DEPEXT) + @rm -f $(BUILDDIR)/$*.$(DEPEXT).tmp + +#Non-File Targets +.PHONY: all remake clean cleaner resources diff --git a/run.sh b/run.sh new file mode 100644 index 0000000..4cd8284 --- /dev/null +++ b/run.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash + +BOLD="\033[1m" +RESET="\033[0m" + +SUCCESS="\033[38;2;80;220;100m" +FAIL="\033[38;2;255;90;90m" +INFO="\033[38;2;100;200;255m" +DIM="\033[2m" + +LOG="./out/out.log" + +# Start +printf "${DIM}────────────────────────────────────────${RESET}\n" +printf "${INFO}${BOLD}> Running...${RESET}\n" +printf "${DIM}────────────────────────────────────────${RESET}\n\n" + +# Send both stdout and stderr to the console and the log file. +start=$(date +%s.%N) +./out/out "$@" 2>&1 | tee "$LOG" +status=${PIPESTATUS[0]} +end=$(date +%s.%N) +elapsed=$(awk "BEGIN { printf \"%.3f\", $end - $start }") + +# Status! +printf "\n${DIM}────────────────────────────────────────${RESET}\n" +if (( status == 0 )); then + printf "${SUCCESS}${BOLD}✓ Success${RESET} (exit %d)\n" "$status" +else + printf "${FAIL}${BOLD}✗ Failed${RESET} (exit %d)\n" "$status" +fi +printf "${INFO}${BOLD}> Time${RESET} ${elapsed}s\n" +printf "${INFO}${BOLD}> Log${RESET} %s\n" "$LOG" +printf "${DIM}────────────────────────────────────────${RESET}" +exit "$status" diff --git a/src/kmake/kmake.cpp b/src/kmake/kmake.cpp new file mode 100644 index 0000000..b7a23fc --- /dev/null +++ b/src/kmake/kmake.cpp @@ -0,0 +1,8 @@ +#include "kmake.hpp" + +#include + +int main() { + std::cout << "hello" << std::endl; + return 0; +} diff --git a/src/kmake/kmake.hpp b/src/kmake/kmake.hpp new file mode 100644 index 0000000..c5a2f4a --- /dev/null +++ b/src/kmake/kmake.hpp @@ -0,0 +1,15 @@ +#pragma once + +#include + +#include +#include +#include +#include +#include + +namespace kmake { + + using namespace ckitty; + +} diff --git a/src/kmake/project/Distro.cpp b/src/kmake/project/Distro.cpp new file mode 100644 index 0000000..c27899d --- /dev/null +++ b/src/kmake/project/Distro.cpp @@ -0,0 +1,9 @@ +#include "Distro.hpp" + +namespace kmake { + + Distro::Distro() {} + + Distro::~Distro() {} + +} diff --git a/src/kmake/project/Distro.hpp b/src/kmake/project/Distro.hpp new file mode 100644 index 0000000..8ec3564 --- /dev/null +++ b/src/kmake/project/Distro.hpp @@ -0,0 +1,29 @@ +#pragma once + +#include + +namespace kmake { + + class Distro { + public: + + map vars; + vector start; + vector deps; + vector compile; + vector link; + vector end; + + public: + + Distro(); + + ~Distro(); + + public: + + + + }; + +} diff --git a/src/kmake/project/Project.cpp b/src/kmake/project/Project.cpp new file mode 100644 index 0000000..a29fa7b --- /dev/null +++ b/src/kmake/project/Project.cpp @@ -0,0 +1,232 @@ +#include "Project.hpp" + +#include + +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace ryml { + using namespace c4::yml; + using namespace c4; +} + +namespace kmake { + + using namespace ryml; + + // Safely converts c4::csubstr view to string + static string to_string(csubstr s) { + if (s.str == nullptr || s.len == 0) return ""; + return string(s.str, s.len); + } + + // Extracts a string list and logs unexpected nodes/types + static void parse_string_list(ryml::ConstNodeRef node, vector& dest, const string& path, vector& log) { + if (!node.valid()) { + log.push_back("[WARN] Section missing at path: '" + path + "'"); + return; + } + + if (!node.is_seq()) { + log.push_back("[WARN] Expected a list/sequence at '" + path + "', but found a scalar or map."); + return; + } + + size_t index = 0; + for (ryml::ConstNodeRef child : node.children()) { + string item_path = path + "[" + std::to_string(index++) + "]"; + + // Unexpected: List item is a map or nested list instead of a string value + if (!child.has_val()) { + log.push_back("[WARN] Expected scalar string at '" + item_path + "', but found a nested container."); + continue; + } + + string val; + child >> val; + + if (val.empty()) { + log.push_back("[WARN] Empty string element ignored at '" + item_path + "'"); + } else { + dest.push_back(val); + } + } + } + + // Extracts a key-value string map and logs unexpected entries/types + static void parse_string_map(ryml::ConstNodeRef node, map& dest, const string& path, vector& log) { + if (!node.valid()) { + log.push_back("[WARN] Section missing at path: '" + path + "'"); + return; + } + + if (!node.is_map()) { + log.push_back("[WARN] Expected key-value map at '" + path + "', but found a scalar or list."); + return; + } + + for (ryml::ConstNodeRef child : node.children()) { + if (!child.has_key()) { + log.push_back("[WARN] Entry without a key found inside map at '" + path + "'"); + continue; + } + + string k = to_string(child.key()); + string key_path = path + "." + k; + + // Unexpected: Value is a sub-map or sub-list instead of a string scalar + if (!child.has_val()) { + log.push_back("[WARN] Expected string scalar for key '" + key_path + "', but found a nested object/list."); + continue; + } + + string v; + child >> v; + + if (v.empty()) { + log.push_back("[WARN] Value for key '" + key_path + "' is empty."); + } + + dest[k] = v; + } + } + + static Distro parse_distro(c4::yml::ConstNodeRef node, const std::string& path, std::vector& log) { + Distro d; + if (node.has_child("vars")) parse_string_map(node["vars"], d.vars, path + ".vars", log); + if (node.has_child("start")) parse_string_list(node["start"], d.start, path + ".start", log); + if (node.has_child("deps")) parse_string_list(node["deps"], d.deps, path + ".deps", log); + if (node.has_child("compile")) parse_string_list(node["compile"], d.compile, path + ".compile", log); + if (node.has_child("link")) parse_string_list(node["link"], d.link, path + ".link", log); + if (node.has_child("end")) parse_string_list(node["end"], d.end, path + ".end", log); + return d; + } + + Project::Project() + : verbosity(Verbosity::NORMAL) { + build.threads = 0; + } + + Project::~Project() {} + + bool Project::load(const string& filename) { + auto logs = vector(); + + // 1. Read file into buffer + std::ifstream file(filename, std::ios::binary | std::ios::ate); + if (!file.is_open()) { + logs.push_back("Failed to open file: " + filename); + return false; + } + + std::streamsize size = file.tellg(); + file.seekg(0, std::ios::beg); + + vector buffer(size); + if (!file.read(buffer.data(), size)) { + logs.push_back("Failed to read file content: " + filename); + return false; + } + + // 2. Parse in-place + ryml::Tree tree = ryml::parse_in_place(ryml::to_substr(buffer)); + ryml::ConstNodeRef root = tree.rootref(); + + if (!root.is_map()) { + logs.push_back("[ERROR] Root YAML document must be a map."); + return; + } + + // Parse 'name' + if (root.has_child("name") && root["name"].has_val()) { + root["name"] >> name; + } else { + logs.push_back("[WARN] Key 'name' is missing or empty."); + } + + // Parse combined 'author' and 'authors' + if (root.has_child("author") && root["author"].has_val()) { + string single_author; + root["author"] >> single_author; + if (!single_author.empty()) { + authors.push_back(single_author); + } + } + if (root.has_child("authors")) { + parse_string_list(root["authors"], authors, "authors", logs); + } + + // Parse 'descr' + if (root.has_child("descr")) { + parse_string_list(root["descr"], descr, "descr", logs); + } + + // Parse 'verbosity' + if (root.has_child("verbosity") && root["verbosity"].has_val()) { + string v_str; + root["verbosity"] >> v_str; + v_str = strings::to_lower(v_str); + if (v_str == "debug") { + verbosity = Verbosity::DEBUG; + } else if (v_str == "high") { + verbosity = Verbosity::HIGH; + } else if (v_str == "low") { + verbosity = Verbosity::LOW; + } else if (v_str == "normal") { + verbosity = Verbosity::NORMAL; + } else { + logs.push_back("[WARN] Unknown verbosity level '" + v_str + "'. Defaulting to 'normal'."); + verbosity = Verbosity::NORMAL; + } + } + + // Parse 'build' + if (root.has_child("build")) { + ryml::ConstNodeRef b_node = root["build"]; + if (b_node.has_child("src")) { + ryml::ConstNodeRef src_node = b_node["src"]; + if (src_node.has_child("path") && src_node["path"].has_val()) { + src_node["path"] >> build.src.path; + } + if (src_node.has_child("targets")) { + parse_string_list(src_node["targets"], build.src.targets, "build.src.targets", logs); + } + } + + if (b_node.has_child("threads") && b_node["threads"].has_val()) { + b_node["threads"] >> build.threads; + } + } + + // Parse 'vars' + if (root.has_child("vars")) { + parse_string_map(root["vars"], vars, "vars", logs); + } + + // Parse 'distros' + if (root.has_child("distros")) { + ryml::ConstNodeRef d_node = root["distros"]; + if (d_node.is_map()) { + for (ryml::ConstNodeRef child : d_node.children()) { + if (child.has_key()) { + string distro_name = to_string(child.key()); + distros[distro_name] = parse_distro(child, "distros." + distro_name, logs); + } + } + } else { + logs.push_back("[WARN] Key 'distros' must be a map of distro configs."); + } + } + } + +} diff --git a/src/kmake/project/Project.hpp b/src/kmake/project/Project.hpp new file mode 100644 index 0000000..ae994ca --- /dev/null +++ b/src/kmake/project/Project.hpp @@ -0,0 +1,44 @@ +#pragma once + +#include + +namespace kmake { + + class Distro; + + enum class Verbosity : int { + DEBUG = 3, HIGH = 2, NORMAL = 1, LOW = 0 + }; + + class Project { + public: + + string name; + vector authors; + vector descr; + Verbosity verbosity; + + struct { + struct { + string path; + vector targets; + } src; + int threads; + } build; + + map vars; + map distros; + + public: + + Project(); + + ~Project(); + + public: + + bool load(const string& filename); + + }; + +}