spotfile 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341
  1. #!/bin/sh
  2. # Copyright (C) 2018 Alister Sanders
  3. # This program is free software: you can redistribute it and/or modify
  4. # it under the terms of the GNU General Public License as published by
  5. # the Free Software Foundation, either version 3 of the License, or
  6. # (at your option) any later version.
  7. # This program is distributed in the hope that it will be useful,
  8. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  10. # GNU General Public License for more details.
  11. # You should have received a copy of the GNU General Public License
  12. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  13. SP_MACRO_PATH="$SP_MACRO_PATH:/usr/share/spotfile"
  14. SP_PKG_PATH="$SP_PKG_PATH:$PWD"
  15. RED="\\x1b[38;5;88m"
  16. BLUE="\\x1b[38;5;69m"
  17. GREEN="\\x1b[38;5;107m"
  18. RESET="\\x1b[0m"
  19. # Utility Functions
  20. # ------------------------------------------------------------------------------
  21. highlight() {
  22. echo -e $(echo "$@" |
  23. sed -e "s,//\([^\(//\)]*\)//,$BLUE\1$RESET,g" \
  24. -e "s,##\([^\(##\)]*\)##,$RED\1$RESET,g")
  25. }
  26. log() {
  27. case "$1" in
  28. error) shift; highlight " $RED!$RESET $@" >&2 ;;
  29. info) shift; highlight " $GREEN*$RESET $@" ;;
  30. verb) shift; [ "$verbose" = 1 ] && highlight "$@" || : ;;
  31. *) ;;
  32. esac
  33. }
  34. die() {
  35. log error "$@"
  36. exit 1
  37. }
  38. find_macro_file() {
  39. fullpath=""
  40. # Check each directory specified in SP_MACRO_PATH for the file
  41. for path in `echo "$SP_MACRO_PATH" | sed -e 's/:/\n/g'`; do
  42. if [ ! -z "$path" -a -f "$path/$1" ]; then
  43. fullpath="$path/$1"
  44. break
  45. fi
  46. done
  47. if [ ! -z "$fullpath" ]; then
  48. echo "$fullpath"
  49. return 0
  50. else
  51. return 1
  52. fi
  53. }
  54. require() {
  55. which "$1" &>/dev/null ||
  56. die "Could not find //$1// in //\$PATH// (is it installed?)"
  57. }
  58. strip_json_str() {
  59. # Remove leading/trailing quotation marks
  60. echo -n "$1" | sed -e 's/^"\(.*\)"$/\1/'
  61. }
  62. run_m4() {
  63. working_dir="$1"
  64. target_host="$2"
  65. # TODO: Include spotfile version info
  66. (cd "$working_dir"; while read -r line; do
  67. echo "$line"
  68. done | M4PATH="$SP_MACRO_PATH" m4 -P \
  69. -D SP_BUILDHOST="$(hostname)" \
  70. -D SP_TARGETHOST="$target_host")
  71. }
  72. help() {
  73. echo "$(basename $0) [ARGS] PACKAGE1 PACKAGE2 ..."
  74. echo " -t, --target HOST Add a target host"
  75. echo " -i, --install Install the specified packages"
  76. echo " -S, --no-symlink Don't create symlinks during package"
  77. echo " installation; copy files directly"
  78. echo " -r, --relative Use relative symlinks for installation"
  79. echo " -V, --verbose Print verbose messages"
  80. echo " -h, --help Print help message"
  81. }
  82. # Package operations
  83. # ------------------------------------------------------------------------------
  84. find_package() {
  85. for path in `echo "$SP_PKG_PATH" | sed -e 's/:/\n/g'`; do
  86. if [ ! -z "$path" -a -d "$path/$1" ]; then
  87. fullpath="$path/$1"
  88. break
  89. fi
  90. done
  91. if [ ! -z "$fullpath" ]; then
  92. echo "$fullpath"
  93. return 0
  94. else
  95. return 1
  96. fi
  97. }
  98. build_package() {
  99. pkg="$1"
  100. target_host="$2"
  101. sp_macros="$3"
  102. log verb "Check for existence of //$pkg/// in //\$SP_PKG_PATH//"
  103. pkg_path=$(find_package "$pkg")
  104. [ $? -ne 0 ] &&
  105. die "Could not find //$pkg// in //\$SP_PKG_PATH//" ||
  106. log verb "Found //$pkg//: $pkg_path"
  107. manifest_file="$pkg_path/pkg.json"
  108. [ ! -f "$manifest_file" ] &&
  109. die "//$pkg// doesn't have a package manifest ($manifest_file)!" ||
  110. log verb "Found manifest for //$pkg//: $manifest_file"
  111. manifest=$(cat "$manifest_file" | jq -c)
  112. build_targets=$(echo "$manifest" | jq ".targets")
  113. n_targets=$(echo "$build_targets" | jq length)
  114. log verb "$n_targets target(s) total to build"
  115. if [ "$n_targets" = 0 ]; then
  116. log verb "//$pkg// has no targets --- nothing to do"
  117. return 0
  118. fi
  119. for i in `seq 0 $((n_targets - 1))`; do
  120. # Determine the input and output file for this target
  121. in_file=$(strip_json_str "$(echo "$build_targets" | jq ".[$i].in")")
  122. out_file=$(strip_json_str "$(echo "$build_targets" | jq ".[$i].out")")
  123. log verb "Target wants $in_file -> $out_file"
  124. input_file_path="$pkg_path/$in_file"
  125. output_file_path="$pkg_path/$target_host/$out_file"
  126. output_dir=$(dirname "$output_file_path")
  127. log verb "Check whether $output_dir exists..."
  128. if [ ! -d "$output_dir" ]; then
  129. mkdir -p "$output_dir" &&
  130. log verb "Created directory $output_dir" ||
  131. die "Failed to create $output_dir for //$pkg//"
  132. fi
  133. # Finally, build the target
  134. cat "$sp_macros" "$input_file_path" |
  135. run_m4 "$pkg_path" "$target_host" > "$output_file_path"
  136. done
  137. }
  138. install_package() {
  139. pkg="$1"
  140. target_host="$2"
  141. no_symlink="$3"
  142. relative_symlink="$4"
  143. log verb "Check for existence of //$pkg/// in //\$SP_PKG_PATH//"
  144. pkg_path=$(find_package "$pkg")
  145. [ $? -ne 0 ] &&
  146. die "Could not find //$pkg// in //\$SP_PKG_PATH//" ||
  147. log verb "Found //$pkg//: $pkg_path"
  148. manifest_file="$pkg_path/pkg.json"
  149. [ ! -f "$manifest_file" ] &&
  150. die "//$pkg// doesn't have a package manifest ($manifest_file)!" ||
  151. log verb "Found manifest for //$pkg//: $manifest_file"
  152. manifest=$(cat "$manifest_file" | jq -c)
  153. install_targets=$(echo "$manifest" | jq ".install")
  154. n_targets=$(echo "$install_targets" | jq length)
  155. if [ "$n_targets" = 0 ]; then
  156. log info "//$pkg// has no installation targets... eh?"
  157. return 0
  158. fi
  159. log verb "Check for host directory in $pkg_path"
  160. [ ! -d "$pkg_path/$target_host" ] && die "//$pkg// does not include a configuration for host //$target_host//"
  161. for i in `seq 0 $((n_targets - 1))`; do
  162. source_file=$(strip_json_str $(echo "$install_targets" | jq ".[$i].source"))
  163. dest_file=$(strip_json_str $(echo "$install_targets" | jq ".[$i].destination") | sed -e 's,/$,,')
  164. source_file_path="$pkg_path/$target_host/$source_file"
  165. dest_file_path="${dest_file/#\~/$HOME}"
  166. dest_parent="$(dirname $dest_file_path)"
  167. # Make sure the parent of the destination path actually exists
  168. if [ ! -d "$dest_parent" ]; then
  169. mkdir -p "$dest_parent" &&
  170. log verb "Created $dest_parent" ||
  171. die "Could not create $dest_parent"
  172. fi
  173. log verb "Installation target wants $source_file_path -> $dest_file_path"
  174. # Any trailing slashes were removed from the path, so it's
  175. # safe to run rm -rf without fear of deleting contents of a
  176. # symlinked directory
  177. if [ -e "$dest_file_path" -o -h "$dest_file_path" ]; then
  178. log info "Removing old $dest_file_path"
  179. rm -rf "$dest_file_path"
  180. fi
  181. if [ "$no_symlink" = 1 ]; then
  182. cp -r "$source_file_path" "$dest_file_path" &&
  183. log info "Copied $source_file_path -> $dest_file_path" ||
  184. die "Failed to copy $source_file_path -> $dest_file_path"
  185. elif [ "$relative_symlink" = 1 ]; then
  186. ln -rs "$source_file_path" "$dest_file_path" &&
  187. log info "Linked $dest_file_path -> $source_file_path" ||
  188. die "Failed to link $dest_file_path -> $source_file_path"
  189. else
  190. ln -s "$source_file_path" "$dest_file_path" &&
  191. log info "Linked $dest_file_path -> $source_file_path" ||
  192. die "Failed to link $dest_file_path -> $source_file_path"
  193. fi
  194. done
  195. }
  196. # Main
  197. # -------------------------------------------------------------------------------
  198. require getopt
  199. require m4
  200. require jq
  201. # Make sure getopt long arguments are supported
  202. getopt -T; [ $? = 4 ] || die "Long getopt arguments are not supported"
  203. # Process command line options
  204. GETOPT=`getopt -o "t:iSrVh" --long "target:,install,no-symlink,relative,verbose,help" -- $*`
  205. eval set -- "$GETOPT"
  206. while :; do
  207. case "$1" in
  208. -t|--target)
  209. # Append this host to the list of target hosts
  210. [ -z "$target_hosts" ] \
  211. && target_hosts="$2" \
  212. || target_hosts="$target_hosts $2"
  213. shift 2
  214. ;;
  215. -i|--install)
  216. do_install=1
  217. shift
  218. ;;
  219. -S|--no-symlink)
  220. no_symlink=1
  221. shift
  222. ;;
  223. -r|--relative)
  224. relative_symlink=1
  225. shift
  226. ;;
  227. -V|--verbose)
  228. verbose=1
  229. shift
  230. ;;
  231. -h|--help)
  232. help
  233. exit 0
  234. ;;
  235. --)
  236. shift
  237. break
  238. ;;
  239. *) ;;
  240. esac
  241. done
  242. # Find the location of the default macro set
  243. spotfile_macros=$(find_macro_file "spotfile.m4")
  244. [ $? -ne 0 ] && die "Could not find //spotfile.m4// in //\$SP_MACRO_PATH//"
  245. [ -z "$do_install" -a "$no_symlink" = 1 ] &&
  246. log error "Warning: //--no-symlink// specified without //--install// (nonsensical)"
  247. [ "$relative_symlink" = 1 -a "$no_symlink" = 1 ] &&
  248. log error "Warning: //--relative// and //--no-symlink// specified (nonsensical)"
  249. if [ -z "$target_hosts" ]; then
  250. log verb "No hosts specified, using local hostname"
  251. target_hosts="$(hostname)"
  252. fi
  253. log verb "Using build host(s) //$target_hosts//"
  254. if [ -z "$do_install" ]; then # Build
  255. # The remaining unshifted arguments are package names
  256. for host in `echo $target_hosts | sed -e 's/:/\n/g'`; do
  257. for pkg in "$@"; do
  258. log info "Working on package //$pkg// for //$host//"
  259. build_package "$pkg" "$host" "$spotfile_macros"
  260. done
  261. done
  262. else # Install configurations
  263. if [ "$(echo $target_hosts | awk '{print $1}')" != "$(echo $target_hosts)" ]; then
  264. die "More than one target host was specified\nOnly a single host should be specified, or none at all"
  265. fi
  266. # Strip whitespace
  267. host="$(echo $target_hosts)"
  268. log info "Using host //$host//"
  269. for pkg in "$@"; do
  270. log info "Installing package //$pkg//"
  271. install_package "$pkg" "$host" "$no_symlink" "$relative_symlink"
  272. done
  273. fi