Automate Haskell development with nixpkgs
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

nix-hs.sh 8.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348
  1. #! @bash@/bin/bash
  2. ################################################################################
  3. #
  4. # This file is part of the package nix-hs. It is subject to the license
  5. # terms in the LICENSE file found in the top-level directory of this
  6. # distribution and at:
  7. #
  8. # git://git.devalot.com/nix-hs.git
  9. #
  10. # No part of this package, including this file, may be copied, modified,
  11. # propagated, or distributed except according to the terms contained in
  12. # the LICENSE file.
  13. ################################################################################
  14. set -e
  15. set -u
  16. ################################################################################
  17. export HASKELL_PROJECT_NAME
  18. export HASKELL_PROJECT_DIR
  19. ################################################################################
  20. option_compiler=default
  21. option_ci_compilers=()
  22. option_profiling=false
  23. option_haddocks=false
  24. option_debug=0
  25. option_nixshell_args=()
  26. option_publish=true
  27. option_import_nixpkgs=true
  28. ################################################################################
  29. usage () {
  30. cat <<EOF
  31. Usage: nix-hs [options] <command>
  32. -c VER Use GHC version VER (also VER,VER,VER,etc.)
  33. -d Enable debugging info for nix-hs
  34. -h This message
  35. -H Enable building haddocks
  36. -I PATH Add PATH to NIX_PATH
  37. -P Don't publish releases [default: publish]
  38. -p Enable profiling [default: off]
  39. -n PATH Shortcut for '-I nixpkgs=PATH' (implies -N)
  40. -N Don't load nix/nixpkgs.nix
  41. Commands:
  42. build: Compile the package (default command)
  43. check: Run some compliance checks on the package
  44. clean: Remove all build artifacts
  45. release: Build and upload a package to Hackage
  46. repl: Start GHCi with the package loaded
  47. shell: Start a nix shell for the package
  48. test: Compile and test the package
  49. EOF
  50. }
  51. ################################################################################
  52. die() {
  53. echo "ERROR:" "$@" > /dev/stderr
  54. exit 1
  55. }
  56. ################################################################################
  57. banner() {
  58. echo
  59. echo "============================================================"
  60. echo "$@"
  61. echo "============================================================"
  62. echo
  63. }
  64. ################################################################################
  65. get_project_name() {
  66. local name
  67. local matches
  68. name=$(ls ./?*.cabal 2> /dev/null)
  69. matches=$(echo "$name" | wc -l)
  70. if [ "$matches" != 1 ]; then
  71. return 1
  72. fi
  73. basename "$name" .cabal
  74. }
  75. ################################################################################
  76. # Search for a project Cabal file, changing to the directory that
  77. # contains it or dies.
  78. find_project() {
  79. local dir
  80. local name
  81. dir=$(pwd)
  82. while [ "$dir" != / ]; do
  83. if name=$(get_project_name); then
  84. break
  85. fi
  86. dir=$(dirname "$dir")
  87. cd "$dir"
  88. done
  89. if [ "$dir" = / ]; then
  90. die "cannot find the .cabal file for this project"
  91. else
  92. HASKELL_PROJECT_NAME="$name"
  93. HASKELL_PROJECT_DIR="$dir"
  94. fi
  95. }
  96. ################################################################################
  97. # Create a proper GHC version string as used in nixpkgs.
  98. set_compiler_version() {
  99. local versions=()
  100. mapfile -t versions < <(echo "$1" | sed -E 's/,/\n/g')
  101. if [ "${#versions[@]}" -gt 1 ]; then
  102. for ver in "${versions[@]}"; do
  103. option_ci_compilers+=( "$(echo "$ver" | tr -d '.')" )
  104. done
  105. option_compiler=${option_ci_compilers[0]}
  106. else
  107. option_compiler=$(echo "$1" | tr -d '.')
  108. fi
  109. }
  110. ################################################################################
  111. nix_shell() {
  112. local extra_options=()
  113. if [ -e nix/nixpkgs.nix ] && [ "$option_import_nixpkgs" = true ]; then
  114. extra_options+=("--arg")
  115. extra_options+=("pkgs")
  116. extra_options+=("import $(pwd)/nix/nixpkgs.nix")
  117. fi
  118. if [ "$option_debug" -eq 1 ]; then
  119. extra_options+=("--show-trace")
  120. fi
  121. nix-shell "${option_nixshell_args[@]}" "${extra_options[@]}" "$@"
  122. }
  123. ################################################################################
  124. nix_shell_extra() {
  125. nix_shell --pure "$@" \
  126. --argstr file "$(pwd)/default.nix" \
  127. --argstr compiler "$option_compiler" \
  128. --arg profiling "$option_profiling" \
  129. @interactive@
  130. }
  131. ################################################################################
  132. # Create/update the nix file from the cabal file.
  133. prepare_nix_files() {
  134. local cabal_file=${HASKELL_PROJECT_NAME}.cabal
  135. local nix_file=${HASKELL_PROJECT_NAME}.nix
  136. if [ ! -r "$nix_file" ] || [ "$cabal_file" -nt "$nix_file" ]; then
  137. @cabal2nix@/bin/cabal2nix . > "$nix_file"
  138. fi
  139. if [ ! -r "default.nix" ]; then
  140. sed -e "s/@NIX_FILE@/${nix_file}/g" \
  141. < @templates@/default.nix > default.nix
  142. fi
  143. }
  144. ################################################################################
  145. # If needed, run `cabal configure'.
  146. cabal_configure() {
  147. local cabal_file=${HASKELL_PROJECT_NAME}.cabal
  148. local datestamp=dist/.configure-run-date
  149. if [ ! -r "$datestamp" ] || [ "$cabal_file" -nt "$datestamp" ]; then
  150. nix_shell_extra --command "do_cabal_configure"
  151. date > dist/.configure-run-date
  152. fi
  153. }
  154. ################################################################################
  155. run_cabal() {
  156. local upload_flags=()
  157. local upload_name="dist/${HASKELL_PROJECT_NAME}-*.tar.gz"
  158. if [ "$option_publish" = true ]; then
  159. upload_flags+=("--publish")
  160. fi
  161. prepare_nix_files
  162. cabal_configure
  163. case "${1:-build}" in
  164. repl)
  165. nix_shell_extra --command "do_cabal_repl lib:$HASKELL_PROJECT_NAME"
  166. ;;
  167. check)
  168. echo "==> packdeps says: (checking dependency versions)"
  169. nix_shell -p haskellPackages.packdeps --run "packdeps ${HASKELL_PROJECT_NAME}.cabal"
  170. echo "==> The tested-with cabal field says: (used for Travis CI)"
  171. grep -i tested-with: "${HASKELL_PROJECT_NAME}.cabal" || :
  172. echo "==> Updating Travis CI configuration file"
  173. nix_shell -p multi-ghc-travis \
  174. --run "make-travis-yml ${HASKELL_PROJECT_NAME}.cabal > .travis.yml"
  175. ;;
  176. release)
  177. run_cabal clean
  178. run_cabal build
  179. nix_shell -p haskellPackages.cabal-install \
  180. --run "cabal sdist"
  181. nix_shell -p haskellPackages.cabal-install \
  182. --run "cabal upload ${upload_flags[*]} $upload_name"
  183. ;;
  184. shell)
  185. nix_shell_extra
  186. ;;
  187. *)
  188. nix_shell_extra --command "do_cabal_$1"
  189. if [ "$option_haddocks" = "true" ]; then
  190. nix_shell_extra --command "do_cabal_haddock"
  191. fi
  192. ;;
  193. esac
  194. }
  195. ################################################################################
  196. command_supports_multiple_runs() {
  197. local command=$1
  198. case "$command" in
  199. build|test|clean|check)
  200. return 0
  201. ;;
  202. *)
  203. return 1
  204. ;;
  205. esac
  206. }
  207. ################################################################################
  208. run_tool() {
  209. local command=$1; shift
  210. if [ "${#option_ci_compilers[@]}" -gt 0 ] && \
  211. command_supports_multiple_runs "$command"
  212. then
  213. for ver in "${option_ci_compilers[@]}"; do
  214. option_compiler="$ver"
  215. banner "GHC: $ver"
  216. run_cabal "clean"
  217. run_cabal "$command" "$@"
  218. done
  219. else
  220. run_cabal "$command" "$@"
  221. fi
  222. }
  223. ################################################################################
  224. # Process the command line:
  225. while getopts "c:dHhI:Ppn:N" o; do
  226. case "${o}" in
  227. c) set_compiler_version "$OPTARG"
  228. ;;
  229. d) option_debug=1
  230. set -x
  231. ;;
  232. H) option_haddocks=true
  233. ;;
  234. h) usage
  235. exit
  236. ;;
  237. I) option_nixshell_args+=("-I" "$OPTARG")
  238. ;;
  239. P) option_publish=false
  240. ;;
  241. p) option_profiling=true
  242. ;;
  243. n) option_nixshell_args+=("-I" "nixpkgs=$OPTARG")
  244. option_import_nixpkgs=false
  245. ;;
  246. N) option_import_nixpkgs=false
  247. ;;
  248. *) exit 1
  249. ;;
  250. esac
  251. done
  252. shift $((OPTIND-1))
  253. ################################################################################
  254. find_project
  255. ################################################################################
  256. # For tools that treat this script like `cabal':
  257. if [ "${1:-build}" = cabal ] && [ "$#" -eq 2 ]; then shift; fi
  258. ################################################################################
  259. # Main dispatch code:
  260. command="${1:-build}"
  261. if [ $# -gt 0 ]; then shift; fi
  262. if [ "${1:-}" = "--" ]; then shift; fi
  263. case "$command" in
  264. new-build|build)
  265. run_tool "build" "$@"
  266. ;;
  267. new-repl|repl)
  268. run_tool "repl" "$@"
  269. ;;
  270. new-test|test)
  271. run_tool "test" "$@"
  272. ;;
  273. shell|clean|check|release)
  274. run_tool "$command" "$@"
  275. ;;
  276. *)
  277. die "unknown command: $1"
  278. ;;
  279. esac