=== shmake_add_module.sh === #!/bin/sh # shmake_module.sh - Module creation for shmake # Author: Emilia Marigold add_module() { check_project_root if [ $# -lt 1 ]; then printf "Usage: %s module [name]\n" "$SCRIPT_NAME" exit 1 fi MODULE_NAME="$1" MODULE_DIR="source_code/MODULE_${MODULE_NAME}" MODULE_NAME_UPPER=$(printf "%s" "$MODULE_NAME" | tr '[:lower:]' '[:upper:]') printf "Creating module '%s'...\n" "$MODULE_NAME" mkdir -p "$MODULE_DIR" mkdir -p "$MODULE_DIR/internal" mkdir -p "$MODULE_DIR/tests" cat > "$MODULE_DIR/${MODULE_NAME}.h" << EOF #ifndef ${MODULE_NAME_UPPER}_H #define ${MODULE_NAME_UPPER}_H /* Function declarations */ #endif /* ${MODULE_NAME_UPPER}_H */ EOF cat > "$MODULE_DIR/${MODULE_NAME}.c" << EOF #include "${MODULE_NAME}.h" /* Function implementations */ EOF cat > "$MODULE_DIR/internal/${MODULE_NAME}_internal.h" << EOF #ifndef ${MODULE_NAME_UPPER}_INTERNAL_H #define ${MODULE_NAME_UPPER}_INTERNAL_H /* Internal function declarations (not exposed in public header) */ #endif /* ${MODULE_NAME_UPPER}_INTERNAL_H */ EOF cat > "$MODULE_DIR/internal/${MODULE_NAME}_internal.c" << EOF #include "${MODULE_NAME}_internal.h" #include "../${MODULE_NAME}.h" /* Internal function implementations */ EOF cat > "$MODULE_DIR/tests/${MODULE_NAME}_test.h" << EOF #ifndef ${MODULE_NAME_UPPER}_TEST_H #define ${MODULE_NAME_UPPER}_TEST_H /* Test function declarations */ #endif /* ${MODULE_NAME_UPPER}_TEST_H */ EOF cat > "$MODULE_DIR/tests/${MODULE_NAME}_test.c" << EOF #include "${MODULE_NAME}_test.h" #include "../${MODULE_NAME}.h" /* Test implementations */ EOF printf "Module '%s' created successfully!\n" "$MODULE_NAME" printf " Location: %s\n" "$MODULE_DIR" printf " Next: Add your implementation to the .c files.\n" printf " Run 'shmake sync' after adding source files to update Makefile.\n" } === shmake_clone_test_library.sh === #!/bin/sh # shmake_testlib.sh - Test library cloning for shmake # Author: Emilia Marigold clone_test_library() { test_lib="$1" LIBRARY_DIR="external_code/test_only/source_code" LIB_URL="" LIB_NAME="" case "$test_lib" in Unity|unity) LIB_URL="https://github.com/ThrowTheSwitch/Unity.git" LIB_NAME="Unity" ;; CMocka|cmocka|CMOCKA) LIB_URL="https://github.com/clibs/cmocka.git" LIB_NAME="cmocka" ;; Check|check) LIB_URL="https://github.com/libcheck/check.git" LIB_NAME="check" ;; None|none) return 0 ;; *) printf "Warning: Unknown test library '%s'. Skipping auto-clone.\n" "$test_lib" return 0 ;; esac printf "\nAuto-cloning test library '%s' to %s...\n" "$test_lib" "$LIBRARY_DIR" if ! command -v git >/dev/null 2>&1; then printf "Warning: 'git' not found. Library '%s' will need to be added manually.\n" "$test_lib" printf "URL: %s\n" "$LIB_URL" mkdir -p "shmake_config" printf "%s\n" "$LIB_URL" > "shmake_config/project_external_libraries.config" return 1 fi if [ -d "$LIBRARY_DIR/$LIB_NAME" ]; then printf "Note: '%s' already exists in %s\n" "$LIB_NAME" "$LIBRARY_DIR" printf "Overwrite? [y/N]: " read -r overwrite case "$overwrite" in y|Y) rm -rf "$LIBRARY_DIR/$LIB_NAME" ;; *) printf "Keeping existing installation.\n" printf "%s\n" "$LIB_URL" > "shmake_config/project_external_libraries.config" return 0 ;; esac fi mkdir -p "$LIBRARY_DIR" printf "Cloning %s (shallow)...\n" "$LIB_NAME" if git clone "$LIB_URL" "$LIBRARY_DIR/$LIB_NAME"; then printf "Test library '%s' cloned successfully!\n" "$test_lib" printf "%s\n" "$LIB_URL" > "shmake_config/project_external_libraries.config" else printf "Warning: Failed to clone '%s'. You may need to add it manually.\n" "$test_lib" printf "URL: %s\n" "$LIB_URL" printf "%s\n" "$LIB_URL" > "shmake_config/project_external_libraries.config" return 1 fi } === shmake_download_library.sh === #!/bin/sh # shmake_library.sh - External library addition for shmake # Author: Emilia Marigold - 2026 download_library() { check_project_root if [ $# -lt 1 ]; then printf "Usage: %s library [url]\n" "$SCRIPT_NAME" exit 1 fi LIBRARY_URL="$1" LIBRARY_DIR="external_code/shared_libraries/source_code" printf "Adding library from '%s' to %s...\n" "$LIBRARY_URL" "$LIBRARY_DIR" LIBRARY_NAME=$(printf "%s" "$LIBRARY_URL" | sed 's|.*/||' | sed 's|.git$||') if [ -d "$LIBRARY_DIR/$LIBRARY_NAME" ]; then printf "Warning: '%s' already exists in %s\n" "$LIBRARY_NAME" "$LIBRARY_DIR" printf "Overwrite? [y/N]: " read -r overwrite case "$overwrite" in y|Y) rm -rf "$LIBRARY_DIR/$LIBRARY_NAME" ;; *) printf "Aborted.\n" && exit 0 ;; esac fi case "$LIBRARY_URL" in *.git) printf "Detected Git repository. Using git clone (shallow)...\n" if command -v git >/dev/null 2>&1; then mkdir -p "$LIBRARY_DIR" git clone "$LIBRARY_URL" "$LIBRARY_DIR/$LIBRARY_NAME" if [ $? -eq 0 ]; then printf "Library '%s' cloned successfully!\n" "$LIBRARY_NAME" printf "%s\n" "$LIBRARY_URL" >> "shmake_config/project_external_libraries.config" else printf "Error: Git clone failed.\n" exit 1 fi else printf "Error: 'git' not found. Please install git.\n" exit 1 fi ;; *) printf "Detected file download. Trying wget/curl...\n" if command -v wget >/dev/null 2>&1; then printf "Using wget...\n" mkdir -p "$LIBRARY_DIR" wget "$LIBRARY_URL" -O "$LIBRARY_DIR/${LIBRARY_NAME}.tar.gz" 2>/dev/null if [ $? -eq 0 ]; then tar -xzf "$LIBRARY_DIR/${LIBRARY_NAME}.tar.gz" -C "$LIBRARY_DIR" 2>/dev/null rm "$LIBRARY_DIR/${LIBRARY_NAME}.tar.gz" printf "Library '%s' downloaded successfully!\n" "$LIBRARY_NAME" printf "%s\n" "$LIBRARY_URL" >> "shmake_config/project_external_libraries.config" else printf "Error: wget download failed.\n" exit 1 fi elif command -v curl >/dev/null 2>&1; then printf "Using curl...\n" mkdir -p "$LIBRARY_DIR" curl -L "$LIBRARY_URL" -o "$LIBRARY_DIR/${LIBRARY_NAME}.tar.gz" 2>/dev/null if [ $? -eq 0 ]; then tar -xzf "$LIBRARY_DIR/${LIBRARY_NAME}.tar.gz" -C "$LIBRARY_DIR" 2>/dev/null rm "$LIBRARY_DIR/${LIBRARY_NAME}.tar.gz" printf "Library '%s' downloaded successfully!\n" "$LIBRARY_NAME" printf "%s\n" "$LIBRARY_URL" >> "shmake_config/project_external_libraries.config" else printf "Error: curl download failed.\n" exit 1 fi else printf "Error: Neither 'wget' nor 'curl' found. Please install one.\n" exit 1 fi ;; esac } === shmake_help_message.sh === #!/bin/sh # shmake_help.sh - Help message for shmake # Author: Emilia Marigold help_message() { printf "USAGE:\n" printf " %s [command] [options]\n\n" "$SCRIPT_NAME" printf "COMMANDS:\n" printf " help Show this help menu\n" printf " init Initialize a new C project structure\n" printf " module [name] Create a new module in source_code/MODULE_name/\n" printf " library [url] Add a library from URL (git clone or wget/curl)\n" printf " link [libs] Add linked libraries (e.g., 'link X11 pthread m')\n" printf " sync Auto-generate Build Configs from source files\n\n" printf "BUILD MODES (via make BUILD_MODE=...):\n" printf " debug Build with debug symbols and debug_only sources\n" printf " release Build with optimizations and release_only sources\n" printf " test Build with test flags and test_only sources\n" printf " (Default: release)\n\n" printf "EXAMPLES:\n" printf " %s\n" "$SCRIPT_NAME" printf " %s init\n" "$SCRIPT_NAME" printf " %s module my_feature\n" "$SCRIPT_NAME" printf " %s library https://github.com/user/repo.git\n" "$SCRIPT_NAME" printf " %s link X11 pthread m\n" "$SCRIPT_NAME" printf " %s sync\n" "$SCRIPT_NAME" printf " make BUILD_MODE=debug\n" } === shmake_init_project.sh === #!/bin/sh # shmake_init.sh - Project initialization for shmake # Author: Emilia Marigold init_project() { printf "Initializing new project...\n\n" read_input "Project Name" "" PROJECT_NAME if [ -z "$PROJECT_NAME" ]; then printf "Error: Project Name cannot be empty.\n" exit 1 fi read_input "C Compiler (gcc, clang, tcc, cc)" "gcc" COMPILER read_input "C Standard (C99, C11, C17, C23)" "C11" C_VERSION read_input "Test Library (Unity, Check, CMOCKA, None)" "None" TEST_LIB printf "\nSelect License:\n" printf " 1) 0BSD\n" printf " 2) AGPL-3.0\n" printf " 3) Apache-2.0\n" printf " 4) BSD-2-Clause\n" printf " 5) BSD-3-Clause\n" printf " 6) BSD-3-Clause-Clear\n" printf " 7) BSL-1.0\n" printf " 8) CC0-1.0\n" printf " 9) CC-BY-4.0\n" printf " 10) CC-BY-SA-4.0\n" printf " 11) EPL-1.0\n" printf " 12) EPL-2.0\n" printf " 13) EUPL-1.2\n" printf " 14) GPL-2.0\n" printf " 15) GPL-3.0\n" printf " 16) ISC\n" printf " 17) LGPL-2.1\n" printf " 18) LGPL-3.0\n" printf " 19) MIT\n" printf " 20) MIT-0\n" printf " 21) MPL-2.0\n" printf " 22) MulanPSL-2.0\n" printf " 23) OFL-1.1\n" printf " 24) OSL-3.0\n" printf " 25) Unlicense\n" printf " 26) UPL-1.0\n" printf " 27) WTFPL\n" printf " 28) Zlib\n" printf " 29) Custom/None\n" printf "\nOption [1-29]: " read -r LICENSE_CHOICE case "$LICENSE_CHOICE" in 1) LICENSE="0BSD" ;; 2) LICENSE="AGPL-3.0" ;; 3) LICENSE="Apache-2.0" ;; 4) LICENSE="BSD-2-Clause" ;; 5) LICENSE="BSD-3-Clause" ;; 6) LICENSE="BSD-3-Clause-Clear" ;; 7) LICENSE="BSL-1.0" ;; 8) LICENSE="CC0-1.0" ;; 9) LICENSE="CC-BY-4.0" ;; 10) LICENSE="CC-BY-SA-4.0" ;; 11) LICENSE="EPL-1.0" ;; 12) LICENSE="EPL-2.0" ;; 13) LICENSE="EUPL-1.2" ;; 14) LICENSE="GPL-2.0" ;; 15) LICENSE="GPL-3.0" ;; 16) LICENSE="ISC" ;; 17) LICENSE="LGPL-2.1" ;; 18) LICENSE="LGPL-3.0" ;; 19) LICENSE="MIT" ;; 20) LICENSE="MIT-0" ;; 21) LICENSE="MPL-2.0" ;; 22) LICENSE="MulanPSL-2.0" ;; 23) LICENSE="OFL-1.1" ;; 24) LICENSE="OSL-3.0" ;; 25) LICENSE="Unlicense" ;; 26) LICENSE="UPL-1.0" ;; 27) LICENSE="WTFPL" ;; 28) LICENSE="Zlib" ;; 29) printf "Enter license name: " read -r LICENSE [ -z "$LICENSE" ] && LICENSE="Custom" ;; *) printf "Invalid selection. Defaulting to Custom\n" LICENSE="Custom" ;; esac TARGETS="linux macos windows bsd ios ipados android" ENABLED_TARGETS="" printf "\nSelect Build Targets (y/n for each):\n" for target in $TARGETS; do printf " Enable %s? [y/n]: " "$target" read -r choice case "$choice" in y|Y) ENABLED_TARGETS="$ENABLED_TARGETS $target" ;; esac done [ -z "$ENABLED_TARGETS" ] && printf "No targets selected. Defaulting to linux.\n" && ENABLED_TARGETS=" linux" printf "\nDo you need a resources folder? [y/n]: " read -r NEED_RESOURCES ENABLED_RESOURCES="" if [ "$NEED_RESOURCES" = "y" ] || [ "$NEED_RESOURCES" = "Y" ]; then printf "\nSelect Resource Sub-directories (y/n for each):\n" RESOURCE_TYPES="fonts images models music sound_effects text textures" for resource in $RESOURCE_TYPES; do printf " Enable %s? [y/n]: " "$resource" read -r choice case "$choice" in y|Y) ENABLED_RESOURCES="$ENABLED_RESOURCES $resource" ;; esac done [ -z "$ENABLED_RESOURCES" ] && printf "No resources selected. Enabling all by default.\n" && ENABLED_RESOURCES=" fonts images models music sound_effects text textures" fi printf "\nVersion Control (git, hg, svn, fossil, bzr, darcs, none): " read -r VCS [ -z "$VCS" ] && VCS="git" VERSION=$(date +"%y.%j.%H%M") printf "\nCreating project folder '%s'...\n" "$PROJECT_NAME" mkdir -p "$PROJECT_NAME" cd "$PROJECT_NAME" || exit 1 printf "Creating subdirectories...\n" mkdir -p "source_code" mkdir -p "external_code/shared_libraries/source_code" mkdir -p "external_code/shared_libraries/binary" mkdir -p "external_code/test_only/source_code" mkdir -p "external_code/test_only/binary" mkdir -p "external_code/debug_only/source_code" mkdir -p "external_code/debug_only/binary" mkdir -p "external_code/release_only/source_code" mkdir -p "external_code/release_only/binary" mkdir -p "build_output" mkdir -p "intermediate_code" mkdir -p "shmake_config" mkdir -p "shmake_config/shmake_cache" for resource in $ENABLED_RESOURCES; do mkdir -p "resources/$resource" done if [ "$LICENSE_CHOICE" != "29" ]; then process_license "$PROJECT_NAME" "$LICENSE" else printf "Custom license selected. Creating stub...\n" cat > "LICENSE" << EOF $LICENSE License Copyright (c) $(date +%Y) $PROJECT_NAME Permission is hereby granted... EOF fi if [ "$TEST_LIB" != "None" ] && [ "$TEST_LIB" != "none" ]; then clone_test_library "$TEST_LIB" fi printf "Creating files...\n" cat > "shmake_config/project.conf" << EOF PROJECT_NAME=$PROJECT_NAME COMPILER=$COMPILER C_VERSION=$C_VERSION TEST_LIB=$TEST_LIB LICENSE=$LICENSE VCS=$VCS VERSION=$VERSION ENABLED_RESOURCES=$ENABLED_RESOURCES EOF cat > "README.md" << EOF # $PROJECT_NAME A C project built with **shmake**. ## Requirements - C Compiler: $COMPILER - C Standard: $C_VERSION Support - Test Library: $TEST_LIB ## License $LICENSE ## Build Run \`./shmake sync\` to generate Makefile configs, then \`make\` to build. ## Build Modes - \`make\` (Default: Release) - \`make BUILD_MODE=debug\` - \`make BUILD_MODE=test\` ## Version $VERSION ## Resources $(for res in $ENABLED_RESOURCES; do printf "- %s\n" "$res"; done) EOF case "$VCS" in git) cat > ".gitignore" << EOF build_output/ intermediate_code/ external_code/ *.swp *.swo *~ .DS_Store Thumbs.db shmake_config/ EOF ;; hg) cat > ".hgignore" << EOF syntax: glob build_output/ intermediate_code/ external_code/ *.swp *.swo *~ .DS_Store Thumbs.db shmake_config/ EOF ;; svn) cat > ".svnignore" << EOF build_output intermediate_code external_code *.swp *.swo *~ .DS_Store Thumbs.db shmake_config EOF ;; *) cat > ".gitignore" << EOF build_output/ intermediate_code/ external_code/ *.swp *.swo *~ .DS_Store shmake_config/ EOF ;; esac cat > "source_code/main.c" << EOF #include int main(void) { printf("Hello, %s!\\n", "$PROJECT_NAME"); printf("Version: %s\\n", "$VERSION"); return 0; } EOF for conf in makefile.conf makefile_test.conf makefile_debug.conf makefile_release.conf makefile_clean.conf; do printf "# %s - Initialized by shmake\n" "$conf" > "shmake_config/$conf" done cat > "shmake_config/linked.conf" << 'EOF' # --- shmake Linked Libraries Configuration --- # Format: library names (automatically converted to -l flags) # Example: LINKED_LIBS = -lX11 -lpthread -lm LINKED_LIBS = EOF cat > "Makefile" << EOF # Generated by shmake init # Run 'shmake sync' to populate build configurations. # Portable: Works on GNU Make, bmake, BSD Make (macOS) include shmake_config/makefile.conf PROJECT_NAME = $PROJECT_NAME .PHONY: all clean all: @echo "Please run 'shmake sync' to generate build rules." @echo "Then run 'make' to build." clean: rm -rf build_output intermediate_code EOF printf "\nProject '%s' initialized successfully!\n" "$PROJECT_NAME" printf " Version: %s\n" "$VERSION" printf " Build Targets: %s\n" "$ENABLED_TARGETS" printf " Resources: %s\n" "$ENABLED_RESOURCES" printf " License: %s\n" "$LICENSE" printf " Test Library: %s\n" "$TEST_LIB" [ "$TEST_LIB" != "None" ] && [ "$TEST_LIB" != "none" ] && printf " Test Library Location: external_code/test_only/source_code/\n" printf " Next steps:\n" printf " 1. Add your source files to source_code/\n" printf " 2. Run 'shmake sync' to generate Makefile build rules\n" printf " 3. Run 'make' to build\n" printf " 4. Use 'make BUILD_MODE=test' for test builds\n" printf " 5. Use 'shmake link X11 pthread m' to add linked libraries\n" } === shmake_link_library.sh === #!/bin/sh # shmake_link.sh - Linked library configuration for shmake # Author: Emilia Marigold add_linked_library() { check_project_root if [ $# -lt 1 ]; then printf "Usage: %s link [library_names]\n" "$SCRIPT_NAME" printf "Example: %s link X11 pthread m\n" "$SCRIPT_NAME" exit 1 fi LIB_NAMES="$*" LIB_FLAGS="" for lib in $LIB_NAMES; do if [ -z "$LIB_FLAGS" ]; then LIB_FLAGS="-l$lib" else LIB_FLAGS="$LIB_FLAGS -l$lib" fi done printf "Adding linked libraries: %s\n" "$LIB_FLAGS" mkdir -p "shmake_config" if [ -f "shmake_config/linked.conf" ]; then printf "Note: 'linked.conf' already exists.\n" printf "Current linked libraries:\n" cat "shmake_config/linked.conf" printf "\nAppend? [y/N]: " read -r append_choice case "$append_choice" in y|Y) old_libs=$(grep '^LINKED_LIBS' "shmake_config/linked.conf" | sed 's/^LINKED_LIBS = //') new_libs="$old_libs $LIB_FLAGS" sed -i '/^LINKED_LIBS/d' "shmake_config/linked.conf" printf "LINKED_LIBS = %s\n" "$new_libs" >> "shmake_config/linked.conf" printf "Linked libraries appended successfully!\n" ;; *) sed -i '/^LINKED_LIBS/d' "shmake_config/linked.conf" printf "LINKED_LIBS = %s\n" "$LIB_FLAGS" >> "shmake_config/linked.conf" printf "Linked libraries overwritten successfully!\n" ;; esac else cat > "shmake_config/linked.conf" << EOF # --- shmake Linked Libraries Configuration --- # Format: library names (automatically converted to -l flags) # Example: LINKED_LIBS = -lX11 -lpthread -lm LINKED_LIBS = $LIB_FLAGS EOF printf "Linked libraries configured successfully!\n" fi printf "\nRun 'shmake sync' to update Makefile with these libraries.\n" } === shmake_process_license.sh === #!/bin/sh # shmake_license.sh - License processing for shmake # Author: Emilia Marigold process_license() { project_name="$1" license_name="$2" script_dir=$(get_script_dir) licenses_dir="$script_dir/../../licenses" printf "Processing license: %s...\n" "$license_name" if [ ! -d "$licenses_dir" ]; then printf "Error: Licenses directory not found at '%s'.\n" "$licenses_dir" printf "Ensure 'shmake' is installed with its 'licenses' folder alongside it.\n" exit 1 fi if [ ! -f "$licenses_dir/$license_name" ]; then printf "Warning: License file '%s' not found in script directory. Creating stub.\n" "$license_name" cat > "LICENSE" << EOF $license_name License Copyright (c) $(date +%Y) $project_name Permission is hereby granted... (See full text for $license_name) EOF return 0 fi cp "$licenses_dir/$license_name" "LICENSE" printf "\nEnter your name (for copyright): " read -r user_name [ -z "$user_name" ] && user_name="Your Name" printf "Enter organization (optional, press Enter to skip): " read -r user_org [ -z "$user_org" ] && user_org="None" printf "Enter project start year: " read -r project_year [ -z "$project_year" ] && project_year=$(date +%Y) current_year=$(date +%Y) escaped_name=$(printf "%s" "$user_name" | sed 's/[&/\]/\\&/g') escaped_org=$(printf "%s" "$user_org" | sed 's/[&/\]/\\&/g') tmp_file=$(mktemp) sed \ -e "s|(name)|$escaped_name|g" \ -e "s|(year)|$current_year|g" \ -e "s|(year1)|$project_year|g" \ -e "s|(year2)|$current_year|g" \ -e "s|(project_name)|$project_name|g" \ -e "s|(org)|$escaped_org|g" \ "LICENSE" > "$tmp_file" mv "$tmp_file" "LICENSE" printf "License '%s' applied successfully!\n" "$license_name" } === shmake.sh === #!/bin/sh # shmake - the "Simple helpful make system" aka "the shell make system" # A POSIX-compliant build system for C projects # Author: Emilia Marigold # License: AGPL-3.0 set -u SCRIPT_NAME="shmake" # Get script directory once SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd) # Source all utility scripts . "$SCRIPT_DIR/shmake_source/shmake_utils.sh" . "$SCRIPT_DIR/shmake_source/shmake_help_message.sh" . "$SCRIPT_DIR/shmake_source/shmake_init_project.sh" . "$SCRIPT_DIR/shmake_source/shmake_add_module.sh" . "$SCRIPT_DIR/shmake_source/shmake_download_library.sh" . "$SCRIPT_DIR/shmake_source/shmake_link_library.sh" . "$SCRIPT_DIR/shmake_source/shmake_sync_makefile.sh" . "$SCRIPT_DIR/shmake_source/shmake_process_license.sh" #========================================================================================= # Main Entry Point #========================================================================================= main() { if [ $# -eq 0 ]; then help_message exit 0 fi case "$1" in help|--help|-h) help_message ;; init) init_project ;; module) shift add_module "$@" ;; library) shift add_library "$@" ;; link) shift add_linked_library "$@" ;; sync) sync_project ;; *) printf "Command unknown: %s\n" "$1" printf "Run '%s help' for usage.\n" "$SCRIPT_NAME" exit 1 ;; esac } main "$@" === shmake_sync_makefile.sh === #!/bin/sh # shmake_sync.sh - Project synchronization for shmake # Author: Emilia Marigold sync_project() { check_project_root printf "Syncing project files...\n" SOURCE_FILES_LIST=$(mktemp) SHARED_SOURCE_LIST=$(mktemp) SHARED_BINARY_LIST=$(mktemp) TEST_SOURCE_LIST=$(mktemp) TEST_BINARY_LIST=$(mktemp) DEBUG_SOURCE_LIST=$(mktemp) DEBUG_BINARY_LIST=$(mktemp) RELEASE_SOURCE_LIST=$(mktemp) RELEASE_BINARY_LIST=$(mktemp) INCLUDE_DIRS_LIST=$(mktemp) LINKED_LIBS_LIST=$(mktemp) trap 'rm -f "$SOURCE_FILES_LIST" "$SHARED_SOURCE_LIST" "$SHARED_BINARY_LIST" "$TEST_SOURCE_LIST" "$TEST_BINARY_LIST" "$DEBUG_SOURCE_LIST" "$DEBUG_BINARY_LIST" "$RELEASE_SOURCE_LIST" "$RELEASE_BINARY_LIST" "$INCLUDE_DIRS_LIST" "$LINKED_LIBS_LIST"' EXIT printf "Scanning source_code/ for source files...\n" [ -d "source_code" ] && find source_code -type f -name '*.c' 2>/dev/null | while read -r f; do printf '%s\n' "$f" >> "$SOURCE_FILES_LIST" done printf "Scanning external_code/shared_libraries/ for source files...\n" [ -d "external_code/shared_libraries/source_code" ] && find external_code/shared_libraries/source_code -type f -name '*.c' 2>/dev/null | while read -r f; do case "$f" in *example*) printf "Skipping %s (example code)\n" "$f" >&2 ;; *) printf '%s\n' "$f" >> "$SHARED_SOURCE_LIST" ;; esac done printf "Scanning external_code/shared_libraries/binary/ for binary libraries...\n" [ -d "external_code/shared_libraries/binary" ] && find external_code/shared_libraries/binary -type f \( -name '*.a' -o -name '*.so' -o -name '*.lib' -o -name '*.dylib' \) 2>/dev/null | while read -r f; do printf '%s\n' "$f" >> "$SHARED_BINARY_LIST" done printf "Scanning external_code/test_only/ for source files...\n" [ -d "external_code/test_only/source_code" ] && find external_code/test_only/source_code -type f -name '*.c' 2>/dev/null | while read -r f; do case "$f" in *example*) printf "Skipping %s (example code)\n" "$f" >&2 ;; *) printf '%s\n' "$f" >> "$TEST_SOURCE_LIST" ;; esac done printf "Scanning external_code/debug_only/ for source files...\n" [ -d "external_code/debug_only/source_code" ] && find external_code/debug_only/source_code -type f -name '*.c' 2>/dev/null | while read -r f; do case "$f" in *example*) printf "Skipping %s (example code)\n" "$f" >&2 ;; *) printf '%s\n' "$f" >> "$DEBUG_SOURCE_LIST" ;; esac done [ -d "external_code/debug_only/binary" ] && find external_code/debug_only/binary -type f \( -name '*.a' -o -name '*.so' -o -name '*.lib' -o -name '*.dylib' \) 2>/dev/null | while read -r f; do printf '%s\n' "$f" >> "$DEBUG_BINARY_LIST" done printf "Scanning external_code/release_only/ for source files...\n" [ -d "external_code/release_only/source_code" ] && find external_code/release_only/source_code -type f -name '*.c' 2>/dev/null | while read -r f; do case "$f" in *example*) printf "Skipping %s (example code)\n" "$f" >&2 ;; *) printf '%s\n' "$f" >> "$RELEASE_SOURCE_LIST" ;; esac done [ -d "external_code/release_only/binary" ] && find external_code/release_only/binary -type f \( -name '*.a' -o -name '*.so' -o -name '*.lib' -o -name '*.dylib' \) 2>/dev/null | while read -r f; do printf '%s\n' "$f" >> "$RELEASE_BINARY_LIST" done printf "Auto-detecting include directories across entire project...\n" find . -type d ! -path "*/internal*" ! -path "./.git*" ! -path "./build_output*" ! -path "./intermediate_code*" ! -path "*/examples*" ! -path "*/example*" ! -path "./shmake_config*" 2>/dev/null | while read -r dir; do clean_dir=$(printf "%s" "$dir" | sed 's|^\./||') [ -z "$clean_dir" ] && continue [ "$clean_dir" = "." ] && continue find "$clean_dir" -maxdepth 1 -type f -name '*.h' 2>/dev/null | grep -q . && printf '%s\n' "$clean_dir" >> "$INCLUDE_DIRS_LIST" done printf "Reading linked libraries from shmake_config/linked.conf...\n" [ -f "shmake_config/linked.conf" ] && grep '^LINKED_LIBS' "shmake_config/linked.conf" | sed 's/^LINKED_LIBS = //' | while read -r line; do printf '%s\n' "$line" >> "$LINKED_LIBS_LIST" done printf "Sorting and deduplicating file lists...\n" sort -u "$SOURCE_FILES_LIST" -o "$SOURCE_FILES_LIST" sort -u "$SHARED_SOURCE_LIST" -o "$SHARED_SOURCE_LIST" sort -u "$SHARED_BINARY_LIST" -o "$SHARED_BINARY_LIST" sort -u "$TEST_SOURCE_LIST" -o "$TEST_SOURCE_LIST" sort -u "$TEST_BINARY_LIST" -o "$TEST_BINARY_LIST" sort -u "$DEBUG_SOURCE_LIST" -o "$DEBUG_SOURCE_LIST" sort -u "$DEBUG_BINARY_LIST" -o "$DEBUG_BINARY_LIST" sort -u "$RELEASE_SOURCE_LIST" -o "$RELEASE_SOURCE_LIST" sort -u "$RELEASE_BINARY_LIST" -o "$RELEASE_BINARY_LIST" sort -u "$INCLUDE_DIRS_LIST" -o "$INCLUDE_DIRS_LIST" sort -u "$LINKED_LIBS_LIST" -o "$LINKED_LIBS_LIST" printf "Reading project configuration...\n" if [ -f "shmake_config/project.conf" ]; then . "shmake_config/project.conf" C_VERSION_LOWER=$(printf "%s" "$C_VERSION" | tr '[:upper:]' '[:lower:]') else C_VERSION_LOWER="c11" fi printf "Generating makefile.conf...\n" cat > "shmake_config/makefile.conf" << 'CONF_HEADER' # --- shmake Base Configuration (Always Included) --- CONF_HEADER printf 'CFLAGS = -std=%s\n' "$C_VERSION_LOWER" >> "shmake_config/makefile.conf" printf 'PROJECT_SOURCES = \\\n' >> "shmake_config/makefile.conf" while IFS= read -r file; do [ -n "$file" ] && printf ' %s \\\n' "$file" >> "shmake_config/makefile.conf" done < "$SOURCE_FILES_LIST" printf '\n' >> "shmake_config/makefile.conf" printf 'SHARED_SOURCES = \\\n' >> "shmake_config/makefile.conf" while IFS= read -r file; do [ -n "$file" ] && printf ' %s \\\n' "$file" >> "shmake_config/makefile.conf" done < "$SHARED_SOURCE_LIST" printf '\n' >> "shmake_config/makefile.conf" printf 'SHARED_LIBS = \\\n' >> "shmake_config/makefile.conf" while IFS= read -r file; do [ -n "$file" ] && printf ' %s \\\n' "$file" >> "shmake_config/makefile.conf" done < "$SHARED_BINARY_LIST" printf '\n' >> "shmake_config/makefile.conf" printf 'INCLUDE_DIRS = \\\n' >> "shmake_config/makefile.conf" while IFS= read -r dir; do [ -n "$dir" ] && printf ' -I%s \\\n' "$dir" >> "shmake_config/makefile.conf" done < "$INCLUDE_DIRS_LIST" sed -i '$ s/ \\$//' "shmake_config/makefile.conf" printf "Generating makefile_test.conf...\n" cat > "shmake_config/makefile_test.conf" << 'CONF_HEADER' # --- shmake Test Configuration (Included only if BUILD_MODE=test) --- CONF_HEADER printf 'TEST_SOURCES = \\\n' >> "shmake_config/makefile_test.conf" while IFS= read -r file; do [ -n "$file" ] && printf ' %s \\\n' "$file" >> "shmake_config/makefile_test.conf" done < "$TEST_SOURCE_LIST" printf '\n' >> "shmake_config/makefile_test.conf" printf 'TEST_LIBS = \\\n' >> "shmake_config/makefile_test.conf" while IFS= read -r file; do [ -n "$file" ] && printf ' %s \\\n' "$file" >> "shmake_config/makefile_test.conf" done < "$TEST_BINARY_LIST" printf '\n' >> "shmake_config/makefile_test.conf" printf "Generating makefile_debug.conf...\n" cat > "shmake_config/makefile_debug.conf" << 'CONF_HEADER' # --- shmake Debug Configuration (Included only if BUILD_MODE=debug) --- CONF_HEADER printf 'DEBUG_SOURCES = \\\n' >> "shmake_config/makefile_debug.conf" while IFS= read -r file; do [ -n "$file" ] && printf ' %s \\\n' "$file" >> "shmake_config/makefile_debug.conf" done < "$DEBUG_SOURCE_LIST" printf '\n' >> "shmake_config/makefile_debug.conf" printf 'DEBUG_LIBS = \\\n' >> "shmake_config/makefile_debug.conf" while IFS= read -r file; do [ -n "$file" ] && printf ' %s \\\n' "$file" >> "shmake_config/makefile_debug.conf" done < "$DEBUG_BINARY_LIST" printf '\n' >> "shmake_config/makefile_debug.conf" printf "Generating makefile_release.conf...\n" cat > "shmake_config/makefile_release.conf" << 'CONF_HEADER' # --- shmake Release Configuration (Included only if BUILD_MODE=release) --- CONF_HEADER printf 'RELEASE_SOURCES = \\\n' >> "shmake_config/makefile_release.conf" while IFS= read -r file; do [ -n "$file" ] && printf ' %s \\\n' "$file" >> "shmake_config/makefile_release.conf" done < "$RELEASE_SOURCE_LIST" printf '\n' >> "shmake_config/makefile_release.conf" printf 'RELEASE_LIBS = \\\n' >> "shmake_config/makefile_release.conf" while IFS= read -r file; do [ -n "$file" ] && printf ' %s \\\n' "$file" >> "shmake_config/makefile_release.conf" done < "$RELEASE_BINARY_LIST" printf '\n' >> "shmake_config/makefile_release.conf" printf "Generating makefile_clean.conf...\n" cat > "shmake_config/makefile_clean.conf" << 'CONF_HEADER' # --- shmake Clean Configuration --- CONF_HEADER printf 'CLEAN_TARGETS = build_output\n' >> "shmake_config/makefile_clean.conf" printf 'EXTRA_CLEAN = intermediate_code\n' >> "shmake_config/makefile_clean.conf" printf "Updating Root Makefile...\n" cat > "Makefile" << 'MAKEFILE_TEMPLATE' # --- shmake Root Makefile --- # Generated/Updated by shmake sync # Portable: Works on GNU Make, bmake, BSD Make (macOS), and other POSIX-compliant makes # # Usage: # make # Build with release mode (default) # make BUILD_MODE=debug # Build with debug mode # make BUILD_MODE=test # Build with test mode # make clean # Clean build artifacts # Include project configuration (for PROJECT_NAME) -include shmake_config/project.conf # Include base build configuration (always included) include shmake_config/makefile.conf # Include linked libraries configuration (if exists) -include shmake_config/linked.conf # Determine Build Mode (default: release) BUILD_MODE ?= release # Include mode-specific configuration based on BUILD_MODE ifeq ($(BUILD_MODE),test) include shmake_config/makefile_test.conf endif ifeq ($(BUILD_MODE),debug) include shmake_config/makefile_debug.conf endif ifeq ($(BUILD_MODE),release) include shmake_config/makefile_release.conf endif # --- Variable Merging --- ALL_SOURCES = $(PROJECT_SOURCES) $(SHARED_SOURCES) ifdef TEST_SOURCES ALL_SOURCES += $(TEST_SOURCES) endif ifdef DEBUG_SOURCES ALL_SOURCES += $(DEBUG_SOURCES) endif ifdef RELEASE_SOURCES ALL_SOURCES += $(RELEASE_SOURCES) endif ALL_LIBS = $(SHARED_LIBS) $(LINKED_LIBS) ifdef TEST_LIBS ALL_LIBS += $(TEST_LIBS) endif ifdef DEBUG_LIBS ALL_LIBS += $(DEBUG_LIBS) endif ifdef RELEASE_LIBS ALL_LIBS += $(RELEASE_LIBS) endif ALL_OBJECTS = $(ALL_SOURCES:.c=.o) OUTPUT_DIR = build_output OBJ_DIR = intermediate_code CC = gcc CFLAGS += $(INCLUDE_DIRS) .PHONY: all clean distclean all: $(PROJECT_NAME) $(PROJECT_NAME): $(ALL_OBJECTS) @mkdir -p $(OUTPUT_DIR) $(CC) $(CFLAGS) -o $(OUTPUT_DIR)/$(PROJECT_NAME) $(ALL_OBJECTS) $(ALL_LIBS) $(OBJ_DIR)/%.o: %.c @mkdir -p $(OBJ_DIR)/$(dir $<) $(CC) $(CFLAGS) -c $< -o $@ include shmake_config/makefile_clean.conf clean: rm -rf $(CLEAN_TARGETS) $(EXTRA_CLEAN) distclean: clean rm -rf $(OBJ_DIR) MAKEFILE_TEMPLATE printf "\nSync complete!\n" printf " Updated shmake_config/makefile.conf\n" printf " Updated shmake_config/makefile_test.conf\n" printf " Updated shmake_config/makefile_debug.conf\n" printf " Updated shmake_config/makefile_release.conf\n" printf " Updated shmake_config/makefile_clean.conf\n" printf " Updated Makefile\n" [ -f "shmake_config/linked.conf" ] && printf " Included shmake_config/linked.conf\n" printf "\nFound %d source files\n" "$(wc -l < "$SOURCE_FILES_LIST" | tr -d ' ')" printf "Found %d shared library sources (always included)\n" "$(wc -l < "$SHARED_SOURCE_LIST" | tr -d ' ')" printf "Found %d test sources (BUILD_MODE=test only)\n" "$(wc -l < "$TEST_SOURCE_LIST" | tr -d ' ')" printf "Found %d debug sources (BUILD_MODE=debug only)\n" "$(wc -l < "$DEBUG_SOURCE_LIST" | tr -d ' ')" printf "Found %d release sources (BUILD_MODE=release only)\n" "$(wc -l < "$RELEASE_SOURCE_LIST" | tr -d ' ')" printf "\nAuto-detected %d include directories:\n" "$(wc -l < "$INCLUDE_DIRS_LIST" | tr -d ' ')" while IFS= read -r dir; do printf " -I%s\n" "$dir" done < "$INCLUDE_DIRS_LIST" if [ -s "$LINKED_LIBS_LIST" ]; then printf "\nLinked libraries from linked.conf:\n" while IFS= read -r lib; do printf " %s\n" "$lib" done < "$LINKED_LIBS_LIST" else printf "\nNo linked libraries configured.\n" fi printf "\nUse 'make BUILD_MODE=test' to build with test sources.\n" printf "Use 'make BUILD_MODE=debug' to build with debug sources.\n" } === shmake_utils.sh === #!/bin/sh # shmake_utils.sh - Utility functions for shmake # Author: Emilia Marigold #========================================================================================= # Get script directory (POSIX-compliant) #========================================================================================= get_script_dir() { script_path="$0" case "$script_path" in */*) ;; *) resolved=$(command -v "$0" 2>/dev/null) if [ -n "$resolved" ]; then script_path="$resolved" fi ;; esac case "$script_path" in /*) ;; *) script_path="$(pwd)/$script_path" ;; esac printf "%s" "$(cd "$(dirname "$script_path")" && pwd)" } #========================================================================================= # Safe input reading for POSIX sh #========================================================================================= read_input() { prompt="$1" default="$2" variable_name="$3" if [ -n "$default" ]; then printf "%s [%s]: " "$prompt" "$default" else printf "%s: " "$prompt" fi read -r input_val if [ -z "$input_val" ]; then input_val="$default" fi eval "$variable_name='$input_val'" } #========================================================================================= # Check if running from project root #========================================================================================= check_project_root() { if [ ! -f "shmake_config/project.conf" ]; then printf "Error: Not in a shmake project directory.\n" printf "Run 'shmake init' first or cd into your project folder.\n" exit 1 fi }