build_shar.sh 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348
  1. #!/usr/bin/env bash
  2. #
  3. # Name: build_shar.sh
  4. # Requires: python-virtualenv, gcc, gcc-c++, swig, sharutils, and the
  5. # develepment headers for both python and openssl
  6. #
  7. # This script will use GNU sharutils to build an installable shar archive, with
  8. # an install prefix of "/opt" (unless overridden with the '-p' option). This
  9. # has a couple uses:
  10. #
  11. # 1. Installing salt (by su'ing to root and running "sh /path/to/sharfile")
  12. # 2. To be used as a basis for creating your own salt rpm/deb.
  13. #
  14. # It will fetch libzmq and build it as a pyzmq extension.
  15. #
  16. # IMPORTANT: Unpacking the shar requires uudecode, which is distributed along
  17. # with sharutils. Thus, you should have sharutils installed on any host which
  18. # will need to unpack the shar archive.
  19. #
  20. # The script is capable of building a shar archive using several methods:
  21. #
  22. # 1. Using a custom pip requirements file
  23. # 2. Using an existing salt tarball (downloaded from PyPI)
  24. # 3. Specifying a version number (the script will fetch the requested version
  25. # from PyPI)
  26. #
  27. # Additionally, it is possible to specify a build_id which will be added to the
  28. # shar filename, useful for telling apart individual shars.
  29. #
  30. # By default, the script will download dependencies using pip, but the '-d'
  31. # option can be used to specify directory from which dependencies will be
  32. # sourced. Any missing dependencies will be retrieved with pip.
  33. #
  34. # It is strongly recommended to run this script on a machine which does not
  35. # have any of the Salt dependencies already installed, because if the script
  36. # detects that ZeroMQ is already installed, then pyzmq's setup.py will not
  37. # build a bundled ZeroMQ.
  38. #
  39. # Run the script with -h for usage details.
  40. #
  41. ################################# FUNCTIONS ##################################
  42. function _timestamp {
  43. date "+%Y-%m-%d %H:%M:%S:"
  44. }
  45. function _log {
  46. timestamp=$(_timestamp)
  47. echo "$1" | sed "s/^/$(_timestamp) /" >>"$logfile"
  48. }
  49. # Both echo and log
  50. function _display {
  51. echo "$1"
  52. _log "$1"
  53. }
  54. function _error {
  55. msg="ERROR: $1"
  56. echo "$msg" 1>&2
  57. echo "$(_timestamp) $msg" >>"$logfile"
  58. echo "One or more errors found. See $logfile for details." 1>&2
  59. exit 1
  60. }
  61. function _tolower {
  62. echo "$1" | tr '[:upper:]' '[:lower:]'
  63. }
  64. function _find_tarball {
  65. target=$1
  66. location=$2
  67. _log "Looking for $target tarball in $location"
  68. # We're looking for a tarball starting with "$target-"
  69. len_target=`expr length "$target"`
  70. let len_target=${len_target}+1
  71. matches=()
  72. for filename in `ls "${location}"`
  73. do
  74. case "$filename" in
  75. *tar.*)
  76. filename_lower=$(_tolower "$filename")
  77. target_lower=$(_tolower "$target")
  78. if test ${filename_lower:0:${len_target}} == "${target_lower}-"; then
  79. matches=("${matches[@]}" "$filename")
  80. test ${#matches[@]} -gt 1 && _error "Ambiguous target \"${target}\""
  81. fi
  82. ;;
  83. *) continue;;
  84. esac
  85. done
  86. match=${matches[0]}
  87. if test -n "$match"; then
  88. _log "$target tarball is ${matches[0]}"
  89. echo ${matches[0]}
  90. else
  91. _error "$target tarball was not found in $location"
  92. fi
  93. }
  94. function _requirements_str {
  95. test -n "$1" && echo "${srcdir}/${1}/requirements.txt" || _error 'Missing release string for _requirements_str'
  96. }
  97. function _get_requirements {
  98. if test -z "$requirements"; then
  99. if test -n "${salt_release}"; then
  100. # salt_release is only set at this point if -s was passed, in which
  101. # case the tarball has been unpacked already and we want to grab
  102. # the tarballs from its requirements.txt.
  103. requirements=$(_requirements_str "$salt_release")
  104. fi
  105. else
  106. # Custom requirements were passed via -r
  107. _display "Using custom requirements from $requirements"
  108. fi
  109. _display 'Grabbing source tarballs'
  110. if test -n "$requirements"; then
  111. # Either custom requirements were passed, or a salt tarball was
  112. # provided. Either way, we're going to be telling pip to download
  113. # tarballs using the instructions in the requirements.txt.
  114. output=`pip install $PIP_OPTS --download "$srcdir" --requirement "$requirements"`; return_code=$?
  115. else
  116. # Neither -r nor -s was specified. We are just downloading the current
  117. # version of salt from pip, and letting pip resolve dependencies rather
  118. # than providing them in a requirements.txt.
  119. #
  120. # If -v was provided, then pip will download the specified version,
  121. # otherwise this variable will be blank.
  122. output=`pip install $PIP_OPTS --download "$srcdir" "salt${version}"`; return_code=$?
  123. fi
  124. _log "$output"
  125. test "$return_code" -eq 0 || _error 'Failed to download tarballs. Aborting.'
  126. }
  127. function _unpack_salt_tarball {
  128. _display "Unpacking Salt tarball"
  129. if test -z "$salt_tarball"; then
  130. salt_tarball=$(_find_tarball salt "$srcdir")
  131. salt_release=${salt_tarball%%.tar*}
  132. fi
  133. cd "$srcdir"
  134. rm -rf "$salt_release"
  135. tar xf "$salt_tarball"
  136. test -z "$requirements" && requirements=$(_requirements_str "$salt_release")
  137. }
  138. function _usage {
  139. printf "USAGE: build_shar.sh [-i <build_id>] [-d <dependency_dir>] [-p <prefix>] [-r <requirements file> | -s <alternate salt tarball> | -v <version from pypi>]\n\n" 1>&2
  140. exit 2
  141. }
  142. #################################### MAIN ####################################
  143. # Set up logging
  144. orig_cwd="`pwd`"
  145. logfile="${orig_cwd}/install.`date +%Y%m%d%H%M%S`.log"
  146. echo "Install log location: $logfile"
  147. prefix='/opt'
  148. while getopts d:hi:p:r:s:v: opt; do
  149. case "$opt" in
  150. d)
  151. deps_dir=$OPTARG
  152. test -d "$deps_dir" || _error "Dependencies dir $deps_dir does not exist"
  153. ;;
  154. i)
  155. build_id=$OPTARG;;
  156. p)
  157. prefix=$OPTARG;;
  158. r)
  159. requirements=$OPTARG
  160. test -f "$requirements" || _error "Requirements file $requirements does not exist"
  161. ;;
  162. s)
  163. salt_tarball=$OPTARG
  164. test -f "$salt_tarball" || _error "Salt tarball $salt_tarball does not exist"
  165. ;;
  166. v)
  167. version=$OPTARG
  168. ;;
  169. *) _usage;;
  170. esac
  171. done
  172. case "$prefix" in
  173. /*) ;;
  174. *) _error "Prefix path must be absolute" ;;
  175. esac
  176. # Make sure that only one of -r/-s/-v was specified
  177. opt_count=0
  178. for opt in "$requirements" "$salt_tarball" "$version"; do
  179. test -n "$opt" && let opt_count=$opt_count+1
  180. done
  181. test $opt_count -ge 2 && _usage
  182. # If version was provided, prepend with "==" for later use in pip command
  183. test -n "$version" && version="==${version}"
  184. # Make needed directories
  185. srcdir="${orig_cwd}/src"
  186. pkgdir="${orig_cwd}/pkg"
  187. test -d "$srcdir" || mkdir "$srcdir"
  188. _log "Source directory: $srcdir"
  189. if test -d "$pkgdir"; then
  190. _log "Removing existing package directory $pkgdir"
  191. rm -rf "$pkgdir"
  192. fi
  193. _log "Creating package directory $pkgdir"
  194. mkdir "$pkgdir"
  195. # Make sure virtualenv is available
  196. test -z "$VIRTUALENV" && VIRTUALENV=`command -v virtualenv`
  197. test -z "$VIRTUALENV" && _error 'virtualenv not present'
  198. _display "virtualenv == $VIRTUALENV"
  199. if ! test -x "`command -v $VIRTUALENV`"; then
  200. _error "$VIRTUALENV is not executable"
  201. fi
  202. # Make sure we're using an up-to-date version of pip in the virtualenv, as
  203. # older pip versions have issues with pip install --download, silently not
  204. # downloading some tarballs.
  205. cd "$orig_cwd"
  206. venv_name=`mktemp -d venv.XXXXXX`
  207. venv_path="${orig_cwd}/${venv_name}"
  208. _display "Creating temp virtualenv at $venv_path"
  209. output=`"$VIRTUALENV" $venv_name`
  210. _log "$output"
  211. _display "Activating temp virtualenv"
  212. source "${venv_path}/bin/activate"
  213. _display "Updating pip in temp virtualenv"
  214. output=`pip install --upgrade pip`
  215. _log "$output"
  216. # Check if wheel is supported in current version of pip
  217. pip help install 2>/dev/null | egrep --quiet '(--)no-use-wheel' && PIP_OPTS='--no-use-wheel' || PIP_OPTS=''
  218. # Make sure swig is available
  219. test -z "$SWIG" && SWIG=`command -v swig`
  220. test -z "$SWIG" && _error 'swig not present'
  221. _display "swig == $SWIG"
  222. if ! test -x "`command -v $SWIG`"; then
  223. _error "$SWIG is not executable"
  224. fi
  225. # Make sure gcc, g++, and sharutils are available
  226. test -n "`command -v gcc`" && _display 'gcc found' || _error 'gcc not installed'
  227. test -n "`command -v g++`" && _display 'g++ found' || _error 'g++ not installed'
  228. test -n "`command -v shar`" && _display 'sharutils found' || _error 'sharutils not installed'
  229. INSTALL="python setup.py install --root=${pkgdir} --prefix=${prefix}"
  230. if test -n "$salt_tarball"; then
  231. cp "$salt_tarball" "$srcdir" || _error "Unable to copy salt tarball to $srcdir"
  232. salt_tarball=`basename "$salt_tarball"`
  233. salt_release=${salt_tarball%%.tar*}
  234. _unpack_salt_tarball
  235. _get_requirements
  236. else
  237. _get_requirements
  238. _unpack_salt_tarball
  239. fi
  240. _display "Deactivating temp virtualenv"
  241. deactivate
  242. _display "Destroying temp virtualenv at $venv_path"
  243. rm -rf "$venv_path"
  244. _display "Reading requirements from $requirements"
  245. deps=()
  246. for dep in `cat "$requirements" | awk '{print $1}'`; do
  247. test "$dep" == 'salt' && continue
  248. deps=("${deps[@]}" "$dep")
  249. done
  250. for dep in "${deps[@]}"; do
  251. _display "Dependency found: $dep"
  252. done
  253. # Install the deps
  254. for dep in "${deps[@]}"; do
  255. tarball=$(_find_tarball "$dep" "$srcdir")
  256. if test -n "$deps_dir"; then
  257. deps_dir_tarball=$(_find_tarball "$dep" "$deps_dir")
  258. if test -n "$deps_dir_tarball"; then
  259. _display "Using dependency tarball from $deps_dir for $dep"
  260. rm -f "$tarball"
  261. cp "${deps_dir}/${deps_dir_tarball}" "$srcdir"
  262. tarball="${deps_dir_tarball}"
  263. fi
  264. fi
  265. cd "$srcdir"
  266. src=${tarball%%.tar*}
  267. _display "Unpacking $src"
  268. rm -rf $src
  269. tar xf $tarball
  270. cd "$src"
  271. # Fetch libzmq so bundled build works on CentOS 5
  272. if test "${src:0:5}" == 'pyzmq'; then
  273. zeromq_spec="bundled/zeromq/zeromq.spec"
  274. if ! test -f "$zeromq_spec"; then
  275. _display "Fetching libzmq"
  276. output=`python setup.py fetch_libzmq 2>&1`; return_code=$?
  277. test "$return_code" -eq 0 || _error 'Failed to fetch libzmq. Aborting.'
  278. else
  279. _display "Bundled ZeroMQ detected"
  280. fi
  281. zeromq_version=`egrep '^Version' "$zeromq_spec" | awk '{print $2}'`
  282. _display "ZeroMQ version: $zeromq_version"
  283. fi
  284. _display "Installing $src"
  285. if test "${src:0:8}" == 'M2Crypto'; then
  286. arch=`uname -m`
  287. output=`env SWIG_FEATURES="-cpperraswarn -includeall -D__${arch}__ -I/usr/include/openssl" $INSTALL 2>&1`; return_code=$?
  288. else
  289. output=`$INSTALL 2>&1`; return_code=$?
  290. fi
  291. _log "$output"
  292. test "$return_code" -eq 0 || _error "Failed to install $src. Aborting."
  293. done
  294. # Install salt
  295. cd "${srcdir}/${salt_release}"
  296. _display "Installing $salt_release"
  297. output=`$INSTALL 2>&1`; return_code=$?
  298. _log "$output"
  299. test "$return_code" -eq 0 || _error "Failed to install $salt_release. Aborting."
  300. _log 'Compressing man pages'
  301. output=`find "${pkgdir}${prefix}/share/man" -type f -not -name '*.gz' -exec gzip {} \;`
  302. _log "$output"
  303. # Everything worked, make the shar
  304. test -n "$build_id" && build_id="-${build_id}"
  305. pkg="${orig_cwd}/${salt_release}${build_id}.shar"
  306. sharlog="${pkg}.log"
  307. _display "Packaging Salt... Destination: $pkg"
  308. _display "shar log will be written to $sharlog"
  309. cd "$pkgdir"
  310. shar ${prefix#/} >"$pkg" 2>"$sharlog"
  311. test "$?" -eq 0 || _error 'shar file build failed'
  312. # Done!
  313. _display "Build of $pkg complete! Nice!"