Skip to content

Instantly share code, notes, and snippets.

@wheresjames
Last active October 17, 2023 22:51
Show Gist options
  • Save wheresjames/a34513d28679b4129084b21c0b01b8a6 to your computer and use it in GitHub Desktop.
Save wheresjames/a34513d28679b4129084b21c0b01b8a6 to your computer and use it in GitHub Desktop.
CMake build script for Visual Studio
#!/bin/bash
# http://github.com/wheresjames
# Run this from the command line like
#
# $ ~/path/to/cmake-tasks.sh clean-config-build release build ./some/cmake/project
#
# OR
#
# $ cd ./some/cmake/project
# $ ./cmake-tasks.sh clean-config-build release
#
# OR
#
# You can use this in Visual studio by adding something like the
# following to your tasks.json file.
# On linux usually at ~/.config/Code/User/tasks.json
#
# {
# "label": "CMake: Release Rebuild",
# "type": "shell",
# "command": "~/code/tasks/cmake-tasks.sh",
# "args": [
# "clean-config-build",
# "release",
# "build",
# "${fileDirname}",
# "${file}",
# "${workspaceFolder}"
# ],
# "problemMatcher": {
# "fileLocation": ["relative", "~/code/tasks"],
# "pattern": {
# "regexp": "^(.*):(\\d+):(\\d+):\\s+(warning|error|note):\\s*(.*)$",
# "file": 1,
# "line": 2,
# "column": 3,
# "severity": 4,
# "message": 5
# }
# },
# "group": {
# "kind": "build",
# "isDefault": true
# }
# },
#==============================================================================
# Parse arguments
#==============================================================================
ACTIONS=$1
BUILDTYPE=$2
BUILDSUB=$3
FILEDIR=$4
FILENAME=$5
WORKSPACE=$6
if [ -z "$ACTIONS" ]; then
echo "No action specified"
exit 1
fi
if [ -z "$BUILDTYPE" ]; then
BUILDTYPE="release"
fi
if [ -z "$BUILDSUB" ]; then
BUILDSUB="build"
fi
#==============================================================================
# Functions
#==============================================================================
# Include utils
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
. "${SCRIPT_DIR}/utils.sh"
#==============================================================================
# Variables
#==============================================================================
# Strip part of path below components with ':' or '=' in it
# Example:
# /run/user/1000/gvfs/sftp:host=192.168.50.127/Users/bob/code/some/path
# becomes
# /Users/bob/code/some/path
FILEDIR=$(strip_path "$FILEDIR" [:=])
FILENAME=$(strip_path "$FILENAME" [:=])
WORKSPACE=$(strip_path "$WORKSPACE" [:=])
# Find cmake
CMAKEEXE=
CMAKEPATHS="$(which cmake),/usr/bin/cmake,/usr/local/bin/cmake,/opt/homebrew/bin/cmake"
for path in ${CMAKEPATHS//,/ }; do
if [ -f "$path" ]; then
CMAKEEXE="$path"
break
fi
done
if [ -z "$CMAKEEXE" ]; then
echo "[ERROR] !!! Could not find cmake executable"
exit 1
fi
echo "-------------------------------------------"
echo "[::] CMake executable : $CMAKEEXE"
echo "[::] Actions : $ACTIONS"
echo "[::] Build type : $BUILDTYPE"
echo "[::] File directory : $FILEDIR"
echo "[::] File name : $FILENAME"
echo "[::] Workspace : $WORKSPACE"
echo "[::] Script directory : $SCRIPT_DIR"
echo "[::] Working directory: $PWD"
echo "-------------------------------------------"
# Look for cmake file in file directory
CMAKEFILE=$(find_file "$FILEDIR,$WORKSPACE,$PWD" \
"CMakeLists.txt" \
".git .editorconfig LICENSE README.md")
if [ -z "$CMAKEFILE" ]; then
echo "Could not find CMakeLists.txt"
exit 1
fi
BUILDROOT=$(dirname "$CMAKEFILE")
# Change to cmake file directory
cd "$BUILDROOT"
# Build out directory
BUILDOUT="$BUILDROOT/$BUILDSUB/$BUILDTYPE"
echo "[::] CMake file : $CMAKEFILE"
echo "[::] Build root : $BUILDROOT"
echo "[::] Build out : $BUILDOUT"
echo "-------------------------------------------"
# Do we have a PROJECT.txt file?
# Example PROJECT.txt file
#
# name cmtasks
# version 0.1.0
# description CMake build tasks
# company wheresjames
# author Robert Umbehant
# email cmtasks@wheresjames.com
# url https://github.com/wheresjames/cmtasks
# repo git+ssh://git@github.com/wheresjames/cmtasks.git
# bugs https://github.com/wheresjames/cmtasks/issues
# license MIT
#
PROJECTFILE="$BUILDROOT/PROJECT.txt"
if [ -f "$PROJECTFILE" ]; then
load_config "APP" "$PROJECTFILE"
else
PROJECTFILE=
fi
if [ -z "$APPNAME" ]; then
APPNAME=$(basename "$BUILDROOT")
fi
echo "[::] Project file : $PROJECTFILE"
echo "-------------------------------------------"
dump_vars "^APP" "[::] " " : "
echo "-------------------------------------------"
#==============================================================================
# Actions
#==============================================================================
#------------------------------------------------------------
# Clean
if find_in_str "clean" "$ACTIONS"; then
echo "[::] --- Cleaning ---"
if [ -d "$BUILDOUT" ]; then
echo "[::] Removing $BUILDOUT"
rm -rf "$BUILDOUT"
fi
fi
#------------------------------------------------------------
# Configure
if find_in_str "config" "$ACTIONS"; then
echo "[::] --- Configuring ---"
# search for CMAKE OPTIONS file (CMakeOpts.<$BUILDTYPE>.txt)
OPTS=
OPTSFILE=$(find_file "$BUILDROOT" "CMakeOpts.${BUILDTYPE}.txt")
if [ ! -f "$OPTSFILE" ]; then
OPTSFILE=$(find_file "$BUILDROOT" "CMakeOpts.txt")
fi
if [ ! -f "$OPTSFILE" ]; then
echo "[::] CMakeOpts.txt file not found"
else
OPTS=$(cat "$OPTSFILE")
echo "[::] CMakeOpts.txt : $OPTSFILE"
echo "[::] CMake options : $OPTS"
fi
if [ ! -d "$BUILDOUT" ]; then
mkdir -p "$BUILDOUT"
fi
$CMAKEEXE . -B "$BUILDOUT" -DCMAKE_BUILD_TYPE=$BUILDTYPE $OPTS
fi
#------------------------------------------------------------
# Build
if find_in_str "build" "$ACTIONS"; then
echo "[::] --- Building ---"
if [ ! -d "$BUILDOUT" ]; then
echo "Could not find build directory: $BUILDOUT"
exit 1
fi
$CMAKEEXE --build "$BUILDOUT"
fi
#!/bin/bash
# http://github.com/wheresjames
## Return the name of the operating system
os_name()
{
if [[ "$OSTYPE" == "linux-gnu"* ]]; then
echo "linux"
elif [[ "$OSTYPE" == "darwin"* ]]; then
echo "darwin"
else
echo "$OSTYPE"
fi
}
## Converts string to all upper case
# @param [0] - String to convert
# @returns string converted to upper case
to_upper()
{
if [[ $(os_name) == "darwin"* ]]; then
echo "$(echo "$1" | awk '{print toupper($0)}')"
else
echo "${1^^}"
fi
}
## Strip leading path while it matches STRIP
# @param [0] PATH - The path to strip
# @param [1] STRIP - The string to strip
strip_path()
{
local PATH=$1
local STRIP=$2
local LAST=
local PREFIX=
while [ "$LAST" != "$PATH" ] && [[ "$PATH" =~ $STRIP ]]; do
LAST="${PATH}"
PATH="${PATH#*/}"
PREFIX="/"
done
echo "${PREFIX}${PATH}"
}
## Find a file in a directory or parent directory
# @param [0] ROOT - Comma separated list of roots to search
# @param [1] FILENAME - The file name to search for
# @param [2] DEEP - List of sub directory names, that if exists
# as a file or directory, use this deeper match
find_file()
{
local ROOT=$1
local FILENAME=$2
local DEEP=$3
local FOUND=
# Search each root
for root in ${ROOT//,/ }; do
while [ ${#root} -gt 2 ] && [ -d "${root}" ]; do
for file in `ls $root`; do
if [ -f $root"/"$file ]; then
if [ "$file" = "$FILENAME" ]
then
if [ -z "$DEEP" ]; then
echo "$root/$file"
return
else
if [ -z "$FOUND" ]; then
FOUND="$root/$file"
else
for sub in ${DEEP// / }; do
if [ -e "$root/$sub" ]; then
FOUND="$root/$file"
fi
done
fi
fi
fi
fi
done
root=$(dirname $root)
done
if [ ! -z "$FOUND" ]; then
echo "$FOUND"
return
fi
done
}
## Find a string in a string
# @param [0] FIND - The string to find
# @param [1] STR - The string to search
# @return The index of the first character of the first occurrence of FIND in STR
find_in_str()
{
local FIND=$1
local STR=$2
if [ -z "$STR" ] || [ -z "$FIND" ]; then
return 1
fi
if [ ${#FIND} -gt ${#STR} ]; then
return 1
fi
local I=0
local LEN=${#STR}
local FINDLEN=${#FIND}
local MAX=$((LEN-FINDLEN))
while [ $I -le $MAX ]; do
if [ "${STR:$I:$FINDLEN}" = "$FIND" ]; then
return 0
fi
I=$((I+1))
done
return 1
}
## Load a config file
# @param [0] PREFIX - Prefix to add to each key
# @param [1] CONFIG_FILE - The config file to load
# @return 0 on success, 1 on failure
# @note This function will set global variables
#
# Example config file
#
# name cmtasks
# version 0.1.0
# description CMake build tasks
# company wheresjames
# author Robert Umbehant
# email cmtasks@wheresjames.com
# url https://github.com/wheresjames/cmtasks
# repo git+ssh://git@github.com/wheresjames/cmtasks.git
# bugs https://github.com/wheresjames/cmtasks/issues
# license MIT
#
load_config()
{
local PREFIX=$1
local CONFIG_FILE=$2
if [ -z "$CONFIG_FILE" ]; then
echo "No config file specified"
return 1
fi
if [ ! -f "$CONFIG_FILE" ]; then
echo "Config file not found: $CONFIG_FILE"
return 1
fi
while read line; do
if [ -z "$line" ]; then
continue
fi
# Skip comments
if [ "${line:0:1}" = "#" ]; then
continue
fi
# Split line into key and value
local KEY=${line%% *}
local VAL=${line#* }
# Convert key to upercase
KEY=$(to_upper "$KEY")
# Trim leading and trailing whitespace
VAL="${VAL#"${VAL%%[![:space:]]*}"}"
# Set key
declare -g "${PREFIX}${KEY}=${VAL}"
done < "$CONFIG_FILE"
}
## Dump all variables matching FIND
# @param [0] FIND - Regex to match variable name
# @param [1] PREFIX - Prefix to add to each output line
# @param [2] PADDING - Padding to add to key name
dump_vars()
{
local FIND=$1
local PREFIX=$2
local PADDING=$3
local KEY=
local KEYSTR=
while IFS= read -r line; do
KEY="${line#declare -[\-a-zA-Z ]* }"
KEY="${KEY%%=*}"
if [[ "$KEY" =~ $FIND ]]; then
if [ ! -z "$PADDING" ]; then
local LKEY=${#KEY}
local LPAD=${#PADDING}
if [ $LKEY -gt $(($LPAD-3)) ]; then
LKEY=$(($LPAD-3))
fi
KEYSTR="${KEY:0:$LKEY}${PADDING:$LKEY}"
else
KEYSTR="${KEY} ? "
fi
echo "${PREFIX}${KEYSTR}${!KEY}"
fi
done < <(declare -p)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment