From 3c0f62d4d8c5a2509c2e51257d5d6720ad6be11c Mon Sep 17 00:00:00 2001
From: "Kenneth Benzie (Benie)" <benie@infektor.net>
Date: Sun, 29 Apr 2018 22:55:35 +0100
Subject: [PATCH] Add build utility commands

* `build` is an alias which on first use invokes `build-dir --build` to
  select a build directory, reconfigure the `build` alias, and invoke
  the `build` alias on the selected build directory.
* `debug` is an alias to the installed system native debugger.
* `build-dir` is a function to select a build directory by reconfiguring
  the `build` alias, it detects `build.ninja` or `Makefile` in the build
  directory and selects the appropriate `ninja` or `make` command.
  Depends on the `build-dir.py` Python script which uses the `pick`
  package to interactively select the build directory.
* `build-run` is a function which builds the specified target then
  attempts to run it, making the assumption it resides in the `bin`
  subdirectory of the build directory.
* `build-debug` is a function which build the specified tared then
  attempts to debug it using the system native debugger, making the
  assumption it resides in the `bin` subdirectory of the build
  directory.
---
 .conduit.yaml          |  2 ++
 build/build-dir.py     | 46 +++++++++++++++++++++++++++
 build/build.plugin.zsh | 71 ++++++++++++++++++++++++++++++++++++++++++
 zshrc                  |  1 +
 4 files changed, 120 insertions(+)
 create mode 100755 build/build-dir.py
 create mode 100644 build/build.plugin.zsh

diff --git a/.conduit.yaml b/.conduit.yaml
index e5c2f6f..806089f 100644
--- a/.conduit.yaml
+++ b/.conduit.yaml
@@ -20,4 +20,6 @@
     - https://github.com/zsh-users/zsh-autosuggestions.git
     - https://github.com/zsh-users/zsh-history-substring-search.git
     - https://github.com/zsh-users/zsh-syntax-highlighting.git
+- pip:
+    - --user pick
 - message: zsh will be the default prompt after next login
diff --git a/build/build-dir.py b/build/build-dir.py
new file mode 100755
index 0000000..8958d63
--- /dev/null
+++ b/build/build-dir.py
@@ -0,0 +1,46 @@
+#!/usr/bin/env python
+"""The pick_build_dir command selects a build directory
+
+The pick_build_dir command scans the current directory for directories starting
+with ``build`` and prompts the user to select one from the list.
+"""
+
+from __future__ import print_function
+
+from argparse import ArgumentParser
+from os import listdir
+from os.path import curdir, isdir
+from sys import stderr
+
+from pick import Picker
+
+
+def main():
+    """Main entry point to build-dir.py script."""
+    parser = ArgumentParser()
+    parser.add_argument('output')
+    parser.add_argument('--default', action='store_true')
+    args = parser.parse_args()
+    directories = []
+    for directory in listdir(curdir):
+        if isdir(directory) and directory.startswith('build'):
+            directories.append(directory)
+    if len(directories) == 0:
+        print('no build directories found', file=stderr)
+        exit(1)
+    build_dirs = sorted(directories)
+    if args.default:
+        build_dir = build_dirs[0]
+    else:
+        picker = Picker(build_dirs, 'Select a build directory:')
+        picker.register_custom_handler(ord(''), lambda _: exit(1))
+        build_dir, _ = picker.start()
+    with open(args.output, 'w') as output:
+        output.write(build_dir)
+
+
+if __name__ == '__main__':
+    try:
+        main()
+    except KeyboardInterrupt:
+        exit(130)
diff --git a/build/build.plugin.zsh b/build/build.plugin.zsh
new file mode 100644
index 0000000..718b15a
--- /dev/null
+++ b/build/build.plugin.zsh
@@ -0,0 +1,71 @@
+# A collection of commands to make it easier to build projects.
+
+# Default `build` alias to select a `build-dir` then invoke a build, using an
+# alias means the configured build command's completion works out of the box.
+alias build="build-dir --build"
+
+# Detect installed debugger and set the `debug` alias to debug a program with
+# command line arguments.
+if [ `uname` = Linux ]; then
+  if which cgdb &> /dev/null; then
+    alias debug='cgdb --args'
+  elif which gdb &> /dev/null; then
+    alias debug='gdb --args'
+  fi
+elif [ `uname` = Darwin ]; then
+  which lldb &> /dev/null && \
+    alias debug='lldb --'
+fi
+
+# Store the path to `build-dir.py`, using `${0:a:h}` does not work in a
+# function because it will result in `pwd` not this scripts directory.
+_build_dir_py=${0:a:h}/build-dir.py
+
+# Prompt user to interactively choose a build directory for `build` to build.
+# TODO: Support arguments:
+# * [x] [-h,--help] - show this help message and exit
+# * [x] [--build] - execute the build commend
+# * [ ] <dir> - start in another directory, not `pwd`
+build-dir() {
+  # Get a unique filename to store the chosen build directory in.
+  [ `uname` = Darwin ] && local file=`mktemp` || local file=`tempfile`
+  # Prompt user to choose a build directory.
+  python $_build_dir_py $file;
+  local error=$?
+  # If the file exists, read the build directory from it, then delete it.
+  [ -f $file ] && build_dir=$PWD/`cat $file`; rm $file
+  # If choosing a build directory failed, return that error.
+  [[ "$error" = "0" ]] || return $error
+  # If `build.ninja` exists in alias `ninja`, return.
+  [ -f $build_dir/build.ninja ] && \
+    local build="ninja -C $build_dir"
+  # If `Makefile` exists in alias `make`, return.
+  if [ -f $build_dir/Makefile ]; then
+    [ `uname` = Darwin ] && \
+      local cpu_count=`sysctl -n hw.ncpu` ||
+      local cpu_count=`grep -c '^processor' /proc/cpuinfo`
+    local build="make -j $cpu_count -C $build_dir"
+  fi
+  # If the build variable is not defined the command could not be determined.
+  if [ -z $build ]; then
+    # TODO: Prompt user to choose a build command?
+    echo "could not determine build command"
+    return 1
+  fi
+  # Redefine the new alias.
+  alias build="$build"
+  # If `--build` is specified then evaluate the command.
+  [[ "$1" = "--build" ]] && eval $build || true
+}
+
+# Build then run a target residing in `$build_dir/bin`.
+build-run() {
+  local target=$1; shift 1
+  build $target && $build_dir/bin/$target $*
+}
+
+# Build then debug a target residing in `$build_dir/bin`.
+build-debug() {
+  local target=$1; shift 1
+  build $target && debug $build_dir/bin/$target $*
+}
diff --git a/zshrc b/zshrc
index 5325294..46546e3 100644
--- a/zshrc
+++ b/zshrc
@@ -13,6 +13,7 @@ source-plugin zsh-autosuggestions
 source-plugin zsh-history-substring-search
 source-plugin zsh-syntax-highlighting
 source-plugin autoenv
+source-plugin build
 
 # Disable non end-of-line autosuggest accept widgets
 ZSH_AUTOSUGGEST_ACCEPT_WIDGETS=(end-of-line vi-end-of-line)