shake

minimal build system that generates Ninja build files

git clone https://9o.is/git/shake.git

commit 3c3c82cde66fae2ea52b371c7c7ba4d2583db1f2
Author: Jul <jul@9o.is>
Date:   Fri,  6 Mar 2026 01:19:37 +0800

init

Diffstat:
Ainstall | 50++++++++++++++++++++++++++++++++++++++++++++++++++
Ashake | 325+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 375 insertions(+), 0 deletions(-)

diff --git a/install b/install @@ -0,0 +1,50 @@ +#!/usr/bin/env sh +set -eu + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +DESTDIR="${DESTDIR-}" +PREFIX="${PREFIX:-/usr/local}" +SHAKE_LIB="/share/shake/lib" + +usage() { + printf "usage: install.sh [options] + --prefix=DIR installation prefix (default: /usr/local) + --destdir=DIR staging directory (for packaging) + --shakelib=DIR path to shake lib (default: /share/shake/lib) + --help show this help +" >&2 + exit 1 +} + +# Parse options +while [ $# -gt 0 ]; do + case "$1" in + --prefix=*) + PREFIX="${1#*=}" + shift + ;; + --destdir=*) + DESTDIR="${1#*=}" + shift + ;; + --shakelib=*) + SHAKE_LIB="${1#*=}" + shift + ;; + --help|-h) + usage + ;; + *) + printf "install: unknown option: %s\n" "$1" >&2 + usage + ;; + esac +done + +set -x + +mkdir -p "${DESTDIR}${PREFIX}/bin" +mkdir -p "${DESTDIR}${PREFIX}${SHAKE_LIB}" +sed "s|%%SHAKE_LIB%%|${DESTDIR}${PREFIX}${SHAKE_LIB}|" \ + "$SCRIPT_DIR/shake" > "${DESTDIR}${PREFIX}/bin/shake" +chmod 0755 "${DESTDIR}${PREFIX}/bin/shake" diff --git a/shake b/shake @@ -0,0 +1,325 @@ +#!/usr/bin/env sh +set -eu + +SHAKE_BIN="$0" +SHAKE_LIB="%%SHAKE_LIB%%" +NINJA_OPTS= +TARGET=. +DIR=. +OUTDIR=. +NINJA_FILE=local.ninja +NINJA_FILES=$DIR/$NINJA_FILE +GEN_FILES="./Shakefile $SHAKE_BIN" +GEN_OUTS=$DIR/$NINJA_FILE + +usage() { + printf "usage: shake [options] [target] + -l dir override shake lib path (default: %%SHAKE_LIB%%) + -C dir change to dir before doing anything + -o dir set the output directory + -n dry run (don't run commands) + -h show this help +" >&2 + exit 1 +} + +while [ $# -gt 0 ]; do + case "$1" in + -l) + [ $# -lt 2 ] && usage + SHAKE_LIB="${2%/}" + shift 2 + ;; + -o) + [ $# -lt 2 ] && usage + OUTDIR="${2%/}" + shift 2 + ;; + -C) + [ $# -lt 2 ] && usage + cd "$2" 2>/dev/null || { + printf "shake: cannot change to directory: %s\n" "$2" >&2 + exit 1 + } + shift 2 + ;; + -n) + NINJA_OPTS="$NINJA_OPTS -n" + shift + ;; + -h|--help) + usage + ;; + -*) + printf "shake: unknown option: %s\n" "$1" >&2 + usage + ;; + *) + [ $# -gt 1 ] && usage + TARGET="$1" + ;; + esac +done + +if [ ! -f Shakefile ]; then + printf "shake: cannot find Shakefile\n" >&2 + exit 1 +fi + +if [ ! -d "$SHAKE_LIB" ]; then + printf "shake: cannot find path: $SHAKE_LIB\n" >&2 + exit 1 +fi + +init_gen() { + var ninja_required_version 1.8 + var builddir $OUTDIR + var dir $DIR + var outdir $OUTDIR + + rule gen "$SHAKE_BIN $dir" + bind description SHAKE $dir + bind generator 1 + bind restat 1 +} + +init_file() { + if persist && [ -e $DIR/$NINJA_FILE.tmp ]; then + rm -f $DIR/$NINJA_FILE.tmp + fi +} + +fini_file() { + _f=$DIR/$NINJA_FILE + if persist && [ -e $_f.tmp ]; then + if cmp -s $_f.tmp $_f; then + rm -f $_f.tmp + else + mv $_f.tmp $_f + fi + fi +} + +fini_gen() { + gen "$GEN_OUTS" '|' $GEN_FILES + phony $dir/ninja $NINJA_FILES + fini_file + wait +} + +import() { + GEN_FILES="$GEN_FILES $DIR/$1" + . $DIR/$1 +} + +set_target() { + TARGET_ROUTE= + + case "$TARGET" in + .|./) + TARGET=. + ;; + '') + return + ;; + *) + TARGET=${1%/} + TARGET=./${TARGET#./} + set_target_route $TARGET + ;; + esac +} + +set_target_route() { + TARGET_ROUTE="$TARGET_ROUTE $1" + if [ $1 = . ]; then return; fi + set_target_route $(dirname $1) +} + +in_target_route() { + [ ! "$TARGET" ] || has $DIR/$1 $TARGET_ROUTE +} + +persist() { + [ ! "$TARGET" ] || [ $TARGET = $DIR ] +} + +write() { + if persist; then + printf '%s\n' "$@" >> $DIR/$NINJA_FILE.tmp + fi +} + +sub() ( + write "subninja $dir/$1.ninja" + NINJA_FILE=$1.ninja + init_file + + [ "${2-}" ] && eval "$2 $1" + $1 + [ "${3-}" ] && eval "$3 $1" + fini_file +) + +shake() { + write "subninja $dir/$1/$NINJA_FILE" + NINJA_FILES="$NINJA_FILES $dir/$1/ninja" + + if in_target_route $1; then + ( + DIR=$DIR/$1 + OUTDIR=$OUTDIR/$1 + NINJA_FILES=$DIR/local.ninja + GEN_FILES="$GEN_FILES $DIR/Shakefile" + GEN_OUTS=$DIR/$NINJA_FILE + + init_file + + var dir $dir/$1 + var outdir $outdir/$1 + + [ "${2-}" ] && eval "$2 $1" + + . $DIR/Shakefile + + [ "${2-}" ] && eval "$3 $1" + + fini_gen + ) & + fi +} + +var() { + _v1=$1 + shift + inline _vs $* + write "$_v1 = $_vs" + eval "$_v1='\$$_v1'" +} + +bind() { + _v1=$1 + shift + inline _vs $* + write " $_v1 = $_vs" +} + +build() { + _v1=$1 + prefix _v2 $outdir $2 + shift 2 + prefix _vs $dir $* + build_parse $_vs + write "build $_v2: $_v1 $_vs $_vd $_vo" +} + +build_parse() { + _vs= + _vd= + _vo= + _mode=vs + + for _v in $*; do + case $_v in + '|') _mode=vd;; + '||') _mode=vo;; + *) + case $_mode in + vs) _vs="$_vs $_v";; + vd) _vd="${_vd:-|} $_v";; + vo) _vo="${_vo:-||} $_v";; + esac + esac + done +} + +rule() { + _rule=$1 + _cmd="$2" + + write "rule $_rule" + bind command "$_cmd" + + _d= + for _v in $_cmd; do + case "$_v" in + *\$*dir/*) _d="${_d:-'|'} \$${_v#*$}";; + esac + done + + eval "$_rule() { _v=\"\$1\"; shift; build $_rule \"\$_v\" \$* $_d; }" +} + +default() { + prefix _v $dir $1 + write "default $_v" +} + +phony() { + prefix _v $dir $1 + shift + write "build $_v: phony $*" +} + +prefix() { + _var=$1 + _pre=$2 + _vs= + shift 2 + + for _v in $*; do + case $_v in + \$* | \|* | /* | .*) ;; + *) _v=$_pre/$_v;; + esac + _vs="$_vs $_v" + done + + eval "$_var='$_vs'" +} + +inline() { + _var=$1 + _vs= + shift + + for _v in $*; do + _vs="$_vs $_v" + done + + eval "$_var='$_vs'" +} + +error() { + printf "Error gen $DIR: %s\n" "$*" + exit 1 +} + +has() { + _val=$1 + shift + for _v in $*; do + if [ $_v = $_val ]; then + return 0 + fi + done + return 1 +} + +foreach() { + while read -r line; do + $2 $line + done <$DIR/$1 + GEN_FILES="$GEN_FILES $DIR/$1" +} + +set_target +init_file +init_gen +trap fini_gen EXIT + +. $DIR/Shakefile + +if [ ! -e build.ninja ]; then + ln -s local.ninja build.ninja +fi