#!/bin/bash # Generic C Project Build Script # Provides convenient build targets with automatic version management # Automatically increments patch version with each build set -e # Exit on any error # Colors for output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # No Color # Function to print colored output print_status() { echo -e "${BLUE}[INFO]${NC} $1" } print_success() { echo -e "${GREEN}[SUCCESS]${NC} $1" } print_warning() { echo -e "${YELLOW}[WARNING]${NC} $1" } print_error() { echo -e "${RED}[ERROR]${NC} $1" } # Detect project name and configuration detect_project_config() { # Use environment variable if set, otherwise derive from directory name if [[ -n "$PROJECT_NAME" ]]; then PROJECT_NAME_LOWER="$PROJECT_NAME" else PROJECT_NAME_LOWER=$(basename "$(pwd)") fi PROJECT_NAME_UPPER=$(echo "$PROJECT_NAME_LOWER" | tr '[:lower:]' '[:upper:]') PROJECT_NAME_CLEAN=$(echo "$PROJECT_NAME_LOWER" | sed 's/[^a-zA-Z0-9_]/_/g') # Detect source directory if [[ -d "src" ]]; then SRC_DIR="src" elif [[ -d "lib" ]]; then SRC_DIR="lib" elif [[ -d "$PROJECT_NAME_LOWER" ]]; then SRC_DIR="$PROJECT_NAME_LOWER" else SRC_DIR="." fi # Set library name LIB_NAME="lib${PROJECT_NAME_CLEAN}.a" } # Function to automatically increment version increment_version() { print_status "Incrementing version..." # Check if we're in a git repository if ! git rev-parse --git-dir > /dev/null 2>&1; then print_warning "Not in a git repository - skipping version increment" return 0 fi # Get the highest version tag (not necessarily the most recent chronologically) LATEST_TAG=$(git tag -l 'v*.*.*' | sort -V | tail -n 1 || echo "v0.1.0") if [[ -z "$LATEST_TAG" ]]; then LATEST_TAG="v0.1.0" fi # Extract version components (remove 'v' prefix if present) VERSION=${LATEST_TAG#v} # Parse major.minor.patch if [[ $VERSION =~ ^([0-9]+)\.([0-9]+)\.([0-9]+)$ ]]; then MAJOR=${BASH_REMATCH[1]} MINOR=${BASH_REMATCH[2]} PATCH=${BASH_REMATCH[3]} else print_error "Invalid version format in tag: $LATEST_TAG" print_error "Expected format: v0.1.0" return 1 fi # Increment patch version NEW_PATCH=$((PATCH + 1)) NEW_VERSION="v${MAJOR}.${MINOR}.${NEW_PATCH}" print_status "Current version: $LATEST_TAG" print_status "New version: $NEW_VERSION" # Create new git tag if git tag "$NEW_VERSION" 2>/dev/null; then print_success "Created new version tag: $NEW_VERSION" # Generate version.h header file cat > "${SRC_DIR}/version.h" << EOF /* * ${PROJECT_NAME_UPPER} - Auto-Generated Version Header * DO NOT EDIT THIS FILE MANUALLY - Generated by build script */ #ifndef ${PROJECT_NAME_UPPER}_VERSION_H #define ${PROJECT_NAME_UPPER}_VERSION_H #define VERSION_MAJOR ${MAJOR} #define VERSION_MINOR ${MINOR} #define VERSION_PATCH ${NEW_PATCH} #define VERSION_STRING "${MAJOR}.${MINOR}.${NEW_PATCH}" #define VERSION_TAG "${NEW_VERSION}" /* Build information */ #define BUILD_DATE "$(date +%Y-%m-%d)" #define BUILD_TIME "$(date +%H:%M:%S)" #define BUILD_TIMESTAMP "$(date '+%Y-%m-%d %H:%M:%S')" /* Git information */ #define GIT_HASH "$(git rev-parse --short HEAD 2>/dev/null || echo 'unknown')" #define GIT_BRANCH "$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo 'unknown')" /* Display versions */ #define VERSION_DISPLAY "${NEW_VERSION}" #define VERSION_FULL_DISPLAY "${NEW_VERSION} ($(date '+%Y-%m-%d %H:%M:%S'), $(git rev-parse --short HEAD 2>/dev/null || echo 'unknown'))" /* Version API functions */ const char* get_version(void); const char* get_version_full(void); const char* get_build_info(void); #endif /* ${PROJECT_NAME_UPPER}_VERSION_H */ EOF # Generate version.c implementation file cat > "${SRC_DIR}/version.c" << EOF /* * ${PROJECT_NAME_UPPER} - Auto-Generated Version Implementation * DO NOT EDIT THIS FILE MANUALLY - Generated by build script */ #include "version.h" const char* get_version(void) { return VERSION_TAG; } const char* get_version_full(void) { return VERSION_FULL_DISPLAY; } const char* get_build_info(void) { return "Built on " BUILD_DATE " at " BUILD_TIME " from commit " GIT_HASH " on branch " GIT_BRANCH; } EOF print_success "Generated version.h and version.c in ${SRC_DIR}/" else print_warning "Tag $NEW_VERSION already exists - using existing version" NEW_VERSION=$LATEST_TAG fi # Update VERSION file for compatibility echo "${NEW_VERSION#v}" > VERSION print_success "Updated VERSION file to ${NEW_VERSION#v}" } # Function to show usage show_usage() { echo "${PROJECT_NAME_UPPER} Build Script" echo "$(printf '=%.0s' $(seq 1 $((${#PROJECT_NAME_UPPER} + 13))))" echo "" echo "Usage: $0 [target] [-m \"commit message\"]" echo "" echo "Available targets:" echo " clean - Clean all build artifacts" echo " build - Build the project (default)" echo " lib - Build static library" echo " examples - Build example programs (if examples/ exists)" echo " test - Run tests (if tests exist)" echo " install - Install library to system" echo " uninstall - Remove library from system" echo " help - Show this help message" echo "" echo "Options:" echo " -m \"message\" - Commit message (required by workspace rules)" echo "" echo "Library output: $LIB_NAME" echo "Source directory: $SRC_DIR/" echo "" echo "Note: Each build automatically increments the patch version." } # Parse command line arguments COMMIT_MESSAGE="" TARGET="" while [[ $# -gt 0 ]]; do case $1 in -m) COMMIT_MESSAGE="$2" shift 2 ;; clean|build|lib|examples|test|install|uninstall|help|--help|-h) TARGET="$1" shift ;; *) if [[ -z "$TARGET" ]]; then TARGET="$1" else print_error "Unknown option: $1" show_usage exit 1 fi shift ;; esac done # Set default target TARGET=${TARGET:-build} # Detect project configuration detect_project_config # Handle targets case "$TARGET" in clean) print_status "Cleaning build artifacts..." if [[ -f "Makefile" ]]; then make clean else rm -f *.a *.so *.o rm -f "${SRC_DIR}/version.h" "${SRC_DIR}/version.c" fi print_success "Clean completed" ;; build|lib) if [[ -n "$COMMIT_MESSAGE" ]]; then print_status "Build message: $COMMIT_MESSAGE" fi increment_version print_status "Building $PROJECT_NAME_UPPER..." if [[ -f "Makefile" ]]; then make clean # Regenerate version files after clean (since clean removes them) if git rev-parse --git-dir > /dev/null 2>&1; then LATEST_TAG=$(git tag -l 'v*.*.*' | sort -V | tail -n 1 || echo "v0.1.0") VERSION=${LATEST_TAG#v} if [[ $VERSION =~ ^([0-9]+)\.([0-9]+)\.([0-9]+)$ ]]; then MAJOR=${BASH_REMATCH[1]} MINOR=${BASH_REMATCH[2]} PATCH=${BASH_REMATCH[3]} # Regenerate version files cat > "${SRC_DIR}/version.h" << EOF /* * ${PROJECT_NAME_UPPER} - Auto-Generated Version Header * DO NOT EDIT THIS FILE MANUALLY - Generated by build script */ #ifndef ${PROJECT_NAME_UPPER}_VERSION_H #define ${PROJECT_NAME_UPPER}_VERSION_H #define VERSION_MAJOR ${MAJOR} #define VERSION_MINOR ${MINOR} #define VERSION_PATCH ${PATCH} #define VERSION_STRING "${MAJOR}.${MINOR}.${PATCH}" #define VERSION_TAG "${LATEST_TAG}" /* Build information */ #define BUILD_DATE "$(date +%Y-%m-%d)" #define BUILD_TIME "$(date +%H:%M:%S)" #define BUILD_TIMESTAMP "$(date '+%Y-%m-%d %H:%M:%S')" /* Git information */ #define GIT_HASH "$(git rev-parse --short HEAD 2>/dev/null || echo 'unknown')" #define GIT_BRANCH "$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo 'unknown')" /* Display versions */ #define VERSION_DISPLAY "${LATEST_TAG}" #define VERSION_FULL_DISPLAY "${LATEST_TAG} ($(date '+%Y-%m-%d %H:%M:%S'), $(git rev-parse --short HEAD 2>/dev/null || echo 'unknown'))" /* Version API functions */ const char* get_version(void); const char* get_version_full(void); const char* get_build_info(void); #endif /* ${PROJECT_NAME_UPPER}_VERSION_H */ EOF cat > "${SRC_DIR}/version.c" << EOF /* * ${PROJECT_NAME_UPPER} - Auto-Generated Version Implementation * DO NOT EDIT THIS FILE MANUALLY - Generated by build script */ #include "version.h" const char* get_version(void) { return VERSION_TAG; } const char* get_version_full(void) { return VERSION_FULL_DISPLAY; } const char* get_build_info(void) { return "Built on " BUILD_DATE " at " BUILD_TIME " from commit " GIT_HASH " on branch " GIT_BRANCH; } EOF print_success "Regenerated version files after clean" fi fi make else print_error "No Makefile found. Please create a Makefile for your project." print_error "The build script works as a wrapper around make." exit 1 fi # Check if library was built if [[ -f "$LIB_NAME" ]] || [[ -f "lib${PROJECT_NAME_CLEAN}.so" ]]; then if [[ -f "$LIB_NAME" ]]; then SIZE=$(stat -c%s "$LIB_NAME" 2>/dev/null || stat -f%z "$LIB_NAME" 2>/dev/null || echo "unknown") print_success "Static library built successfully (${SIZE} bytes)" ls -la "$LIB_NAME" fi if [[ -f "lib${PROJECT_NAME_CLEAN}.so" ]]; then SIZE=$(stat -c%s "lib${PROJECT_NAME_CLEAN}.so" 2>/dev/null || stat -f%z "lib${PROJECT_NAME_CLEAN}.so" 2>/dev/null || echo "unknown") print_success "Shared library built successfully (${SIZE} bytes)" ls -la "lib${PROJECT_NAME_CLEAN}.so" fi else print_warning "No library file found. Check your Makefile configuration." fi ;; examples) if [[ ! -d "examples" ]]; then print_error "No examples/ directory found" exit 1 fi increment_version print_status "Building examples..." if [[ -f "Makefile" ]]; then make clean make if make examples 2>/dev/null; then print_success "Examples built successfully" ls -la examples/ else print_warning "Examples target not found in Makefile" fi else print_error "No Makefile found for building examples" exit 1 fi ;; test) print_status "Running tests..." if [[ -f "Makefile" ]]; then make clean make if make test 2>/dev/null; then print_success "All tests passed" else print_warning "Test target not found in Makefile, checking tests/ directory" if [[ -d "tests" ]]; then cd tests && make test 2>/dev/null && cd .. print_success "Tests completed" else print_error "No tests found" exit 1 fi fi else print_error "No Makefile found for running tests" exit 1 fi ;; install) increment_version print_status "Installing library to system..." if [[ -f "Makefile" ]]; then make clean make sudo make install print_success "Library installed to /usr/local" else print_error "No Makefile found for installation" exit 1 fi ;; uninstall) print_status "Uninstalling library from system..." if [[ -f "Makefile" ]]; then sudo make uninstall print_success "Library uninstalled" else print_error "No Makefile found for uninstallation" exit 1 fi ;; help|--help|-h) show_usage ;; *) print_error "Unknown target: $TARGET" echo "" show_usage exit 1 ;; esac