vk-book/vk.sh
2025-05-23 21:13:53 -04:00

241 lines
6.1 KiB
Bash
Executable file

set -e
SCRIPT_NAME="vk"
PROJECT_ROOT=$(pwd)
BUILD_DIR="$PROJECT_ROOT/build"
function help() {
echo "Vulkan Cookbook Project Management Tool"
echo ""
echo "Usage: $SCRIPT_NAME [command]"
echo ""
echo "Commands:"
echo " new CHAPTER SAMPLE NAME Create a new sample (e.g. vk new 2 1 'GLFW Window')"
echo " build [sample] Build project using Ninja (all or specific sample)"
echo " run CHAPTER SAMPLE Run a specific sample"
echo " cmake Regenerate CMake files"
echo " list List all samples"
echo " compile-commands Copy compile_commands.json to project root"
echo " deps Run deps bootstrapping"
echo " help Show this help message"
}
function new_sample() {
if [ $# -lt 3 ]; then
echo "Error: Missing arguments for 'new'"
echo "Usage: $SCRIPT_NAME new CHAPTER SAMPLE NAME"
return 1
fi
CHAPTER=$1
SAMPLE=$2
NAME=$3
PADDED_CHAPTER=$(printf "%02d" "$CHAPTER")
PADDED_SAMPLE=$(printf "%02d" "$SAMPLE")
SAMPLE_DIR="$PROJECT_ROOT/Chapter$PADDED_CHAPTER/$PADDED_SAMPLE"_"$NAME"
if [ -d "$SAMPLE_DIR" ]; then
echo "Error: Sample directory already exists: $SAMPLE_DIR"
return 1
fi
echo "Creating new sample Ch${PADDED_CHAPTER}_${PADDED_SAMPLE}_${NAME}..."
mkdir -p "$SAMPLE_DIR/src"
# Create CMakeLists.txt
cat >"$SAMPLE_DIR/CMakeLists.txt" <<EOL
cmake_minimum_required(VERSION 3.19)
project(Chapter${PADDED_CHAPTER})
include(../../CMake/CommonMacros.txt)
SETUP_APP(Ch${PADDED_CHAPTER}_${PADDED_SAMPLE}_${NAME} "Chapter ${PADDED_CHAPTER}")
target_include_directories(Ch${PADDED_CHAPTER}_${PADDED_SAMPLE}_${NAME} PUBLIC \${CMAKE_SOURCE_DIR})
target_link_libraries(Ch${PADDED_CHAPTER}_${PADDED_SAMPLE}_${NAME} SharedUtils)
EOL
# Create minimal main.cpp with GLFW boilerplate
cat >"$SAMPLE_DIR/src/main.cpp" <<EOL
#include <cstdint>
#include <iostream>
#include <shared/HelpersGLFW.h>
int main(int argc, char* argv[]) {
std::cout << "Chapter ${CHAPTER}, Sample ${SAMPLE}: ${NAME}" << std::endl;
uint32_t width = 1280;
uint32_t height = 720;
GLFWwindow *window = initWindow("Ch${PADDED_CHAPTER}_${PADDED_SAMPLE}: ${NAME}", width, height);
while (!glfwWindowShouldClose(window)) {
glfwPollEvents();
}
glfwDestroyWindow(window);
glfwTerminate();
return 0;
}
EOL
echo "Successfully created new sample: $SAMPLE_DIR"
echo "Run '$SCRIPT_NAME cmake' to update CMake configuration"
}
function build_project() {
if [ ! -d "$BUILD_DIR" ]; then
echo "Creating build directory..."
mkdir -p "$BUILD_DIR"
fi
cd "$BUILD_DIR"
if [ $# -eq 0 ]; then
echo "Building all targets..."
ninja
else
TARGET="Ch$1"
echo "Building target: $TARGET"
ninja "$TARGET"
fi
cd "$PROJECT_ROOT"
}
function run_sample() {
if [ $# -lt 2 ]; then
echo "Error: Missing arguments for 'run'"
echo "Usage: $SCRIPT_NAME run CHAPTER SAMPLE"
return 1
fi
CHAPTER=$(printf "%02d" "$1")
SAMPLE=$(printf "%02d" "$2")
# Find the exact sample name
SAMPLE_PATTERN="Ch${CHAPTER}_${SAMPLE}_*"
SAMPLE_BINARY=$(find "$PROJECT_ROOT/bin" -name "$SAMPLE_PATTERN" -type f -executable 2>/dev/null | head -1)
if [ -z "$SAMPLE_BINARY" ]; then
echo "Error: Sample not found or not built: Chapter $CHAPTER, Sample $SAMPLE"
echo "Try building it first: $SCRIPT_NAME build"
return 1
fi
echo "Running $SAMPLE_BINARY..."
"$SAMPLE_BINARY"
}
function regenerate_cmake() {
if [ ! -d "$BUILD_DIR" ]; then
mkdir -p "$BUILD_DIR"
fi
# Update root CMakeLists.txt to include all chapters/samples
echo "Updating root CMakeLists.txt with new samples..."
# Find all chapter/sample directories, excluding the build directory
SAMPLES=$(find "$PROJECT_ROOT" -type d -path "*/Chapter*/[0-9][0-9]_*" -not -path "*/**/build/*" -not -path "*/**/src" | sort)
# Create a temporary file with updated subdirectories
TEMP_CMAKE=$(mktemp)
# Extract the content up to line 100 (before add_subdirectory calls)
head -n "$(grep -n "BINARIES" CMakeLists.txt | head -n 1 | cut -d: -f1)" "$PROJECT_ROOT/CMakeLists.txt" >"$TEMP_CMAKE"
# Add subdirectories for all samples
for SAMPLE_DIR in $SAMPLES; do
# Skip src directories
if [[ "$SAMPLE_DIR" == */src ]]; then
continue
fi
# Get relative path from project root
REL_PATH=$(realpath --relative-to="$PROJECT_ROOT" "$SAMPLE_DIR")
echo "add_subdirectory($REL_PATH)" >>"$TEMP_CMAKE"
done
# Update the CMakeLists.txt file
mv "$TEMP_CMAKE" "$PROJECT_ROOT/CMakeLists.txt"
cd "$BUILD_DIR"
echo "Regenerating CMake files..."
cmake -G Ninja ..
cd "$PROJECT_ROOT"
# Copy compile_commands.json to project root
if [ -f "$BUILD_DIR/compile_commands.json" ]; then
cp "$BUILD_DIR/compile_commands.json" "$PROJECT_ROOT/"
echo "Copied compile_commands.json to project root"
fi
}
function update_deps() {
echo "Updating Dependencies"
pushd "$PROJECT_ROOT/deps"
./bootstrap.py
popd
echo "Dependencies updated"
}
function list_samples() {
echo "Available samples:"
find "$PROJECT_ROOT" -type d -path "*/Chapter*/[0-9][0-9]_*" -not -path "build/*" -not -path "*/**/src" | sort | while read -r dir; do
# Skip src directories
if [[ "$dir" == */src ]]; then
continue
fi
echo " $(basename "$(dirname "$dir")")/$(basename "$dir")"
done
}
# Update main CMakeLists.txt if sample is added or removed
function update_root_cmake() {
# Check if we need to update the root CMakeLists.txt
if [ "$1" == "new" ] || [ "$1" == "delete" ]; then
regenerate_cmake
fi
}
# Main command dispatcher
if [ $# -eq 0 ]; then
help
exit 0
fi
COMMAND=$1
shift
case "$COMMAND" in
"new")
new_sample "$@"
update_root_cmake "new"
;;
"build")
build_project "$@"
;;
"run")
run_sample "$@"
;;
"cmake")
regenerate_cmake
;;
"list")
list_samples
;;
"deps")
update_deps
;;
"compile-commands")
cp "$BUILD_DIR/compile_commands.json" "$PROJECT_ROOT/" 2>/dev/null || echo "No compile_commands.json found in build directory"
;;
"help")
help
;;
*)
echo "Unknown command: $COMMAND"
help
exit 1
;;
esac