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.
 
 
 
 

505 lines
14 KiB

  1. #!/bin/sh
  2. #
  3. # Copyright (c) 2018, Julian Ospald <hasufell@posteo.de>
  4. # All rights reserved.
  5. #
  6. # Redistribution and use in source and binary forms, with or without
  7. # modification, are permitted provided that the following conditions are met:
  8. #
  9. # 1. Redistributions of source code must retain the above copyright notice,
  10. # this list of conditions and the following disclaimer.
  11. # 2. Redistributions in binary form must reproduce the above copyright
  12. # notice, this list of conditions and the following disclaimer in the
  13. # documentation and/or other materials provided with the distribution.
  14. # 3. Neither the name of the <ORGANIZATION> nor the names of its
  15. # contributors may be used to endorse or promote products derived from
  16. # this software without specific prior written permission.
  17. #
  18. # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  19. # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  20. # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  21. # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
  22. # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
  23. # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
  24. # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
  25. # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
  26. # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  27. # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  28. # POSSIBILITY OF SUCH DAMAGE.
  29. ## global variables ##
  30. VERSION=0.0.1
  31. SCRIPT="$(basename "$0")"
  32. VERBOSE=false
  33. FORCE=false
  34. INSTALL_BASE="$HOME/.ghcup"
  35. ## print help ##
  36. usage() {
  37. (>&2 echo "ghcup ${VERSION}
  38. GHC up toolchain installer
  39. USAGE:
  40. ${SCRIPT} [FLAGS] <SUBCOMMAND>
  41. FLAGS:
  42. -v, --verbose Enable verbose output
  43. -h, --help Prints help information
  44. -V, --version Prints version information
  45. SUBCOMMANDS:
  46. install Install GHC
  47. show Show current/installed GHC
  48. set Set currently active GHC version
  49. self-update Update this script in-place
  50. ")
  51. exit 1
  52. }
  53. install_usage() {
  54. (>&2 echo "ghcup-install
  55. Install the specified GHC version
  56. USAGE:
  57. ${SCRIPT} install [FLAGS] <VERSION>
  58. FLAGS:
  59. -h, --help Prints help information
  60. -f, --force Overwrite already existing installation
  61. ARGS:
  62. <VERSION> E.g. \"8.4.3\" or \"8.6.1\"
  63. ")
  64. exit 1
  65. }
  66. set_usage() {
  67. (>&2 echo "ghcup-set
  68. Set the currently active GHC to the specified version
  69. USAGE:
  70. ${SCRIPT} set [FLAGS] <VERSION>
  71. FLAGS:
  72. -h, --help Prints help information
  73. ARGS:
  74. <VERSION> E.g. \"8.4.3\" or \"8.6.1\"
  75. ")
  76. exit 1
  77. }
  78. self_update_usage() {
  79. (>&2 echo "ghcup-self-update
  80. Update the ghcup.sh script in-place
  81. USAGE:
  82. ${SCRIPT} self-update [FLAGS] [TARGET-LOCATION]
  83. FLAGS:
  84. -h, --help Prints help information
  85. ARGS:
  86. [TARGET-LOCATION] Where to place the updated script (defaults to ~/.local/bin).
  87. Must be an absolute path!
  88. ")
  89. exit 1
  90. }
  91. show_usage() {
  92. (>&2 echo "ghcup-show
  93. Show the installed/current GHC versions
  94. USAGE:
  95. ${SCRIPT} show [FLAGS]
  96. FLAGS:
  97. -h, --help Prints help information
  98. -i, --installed Show installed GHC version only
  99. ")
  100. exit 1
  101. }
  102. ## utilities ##
  103. die() {
  104. (>&2 echo "$1")
  105. exit 2
  106. }
  107. edo()
  108. {
  109. if ${VERBOSE} ; then
  110. echo "$@" 1>&2
  111. fi
  112. "$@" || exit 2
  113. }
  114. echov() {
  115. if ${VERBOSE} ; then
  116. echo "$1"
  117. else
  118. if [ -n "$2" ] ; then
  119. echov "$2"
  120. fi
  121. fi
  122. }
  123. printf_green() {
  124. printf "\\033[0;32m%s\\033[0m\\n" "$1"
  125. }
  126. get_distro_name() {
  127. if [ -f /etc/os-release ]; then
  128. # freedesktop.org and systemd
  129. # shellcheck disable=SC1091
  130. . /etc/os-release
  131. printf "%s" "$NAME"
  132. elif command -V lsb_release >/dev/null 2>&1; then
  133. # linuxbase.org
  134. printf "%s" "$(lsb_release -si)"
  135. elif [ -f /etc/lsb-release ]; then
  136. # For some versions of Debian/Ubuntu without lsb_release command
  137. # shellcheck disable=SC1091
  138. . /etc/lsb-release
  139. printf "%s" "$DISTRIB_ID"
  140. elif [ -f /etc/debian_version ]; then
  141. # Older Debian/Ubuntu/etc.
  142. printf "Debian"
  143. else
  144. # Fall back to uname, e.g. "Linux <version>", also works for BSD, etc.
  145. printf "%s" "$(uname -s)"
  146. fi
  147. }
  148. get_distro_ver() {
  149. if [ -f /etc/os-release ]; then
  150. # freedesktop.org and systemd
  151. # shellcheck disable=SC1091
  152. . /etc/os-release
  153. printf "%s" "$VERSION_ID"
  154. elif command -V lsb_release >/dev/null 2>&1; then
  155. # linuxbase.org
  156. printf "%s" "$(lsb_release -sr)"
  157. elif [ -f /etc/lsb-release ]; then
  158. # For some versions of Debian/Ubuntu without lsb_release command
  159. # shellcheck disable=SC1091
  160. . /etc/lsb-release
  161. printf "%s" "$DISTRIB_RELEASE"
  162. elif [ -f /etc/debian_version ]; then
  163. # Older Debian/Ubuntu/etc.
  164. printf "%s" "$(cat /etc/debian_version)"
  165. else
  166. # Fall back to uname, e.g. "Linux <version>", also works for BSD, etc.
  167. printf "%s" "$(uname -r)"
  168. fi
  169. }
  170. get_arch() {
  171. myarch=$(uname -m)
  172. case "${myarch}" in
  173. x86_64)
  174. printf "x86_64" # or AMD64 or Intel64 or whatever
  175. ;;
  176. i*86)
  177. printf "i386" # or IA32 or Intel32 or whatever
  178. ;;
  179. *)
  180. die "Cannot figure out architecture (was: ${myarch})"
  181. ;;
  182. esac
  183. unset myarch
  184. }
  185. get_download_url() {
  186. myghcver=$1
  187. myarch=$(get_arch)
  188. mydistro=$(get_distro_name)
  189. mydistrover=$(get_distro_ver)
  190. baseurl="https://downloads.haskell.org/~ghc"
  191. # TODO: awkward, restructure
  192. case "${mydistro},${mydistrover},${myarch},${myghcver}" in
  193. Debian,7,i386,8.2.2)
  194. printf "%s" "${baseurl}/${myghcver}/ghc-${myghcver}-${myarch}-deb${mydistrover}-linux.tar.xz"
  195. ;;
  196. *,*,i386,*)
  197. printf "%s" "${baseurl}/${myghcver}/ghc-${myghcver}-${myarch}-deb8-linux.tar.xz"
  198. ;;
  199. Debian,*,*,8.2.2)
  200. printf "%s" "${baseurl}/${myghcver}/ghc-${myghcver}-${myarch}-deb8-linux.tar.xz"
  201. ;;
  202. Debian,8,*,*)
  203. printf "%s" "${baseurl}/${myghcver}/ghc-${myghcver}-${myarch}-deb8-linux.tar.xz"
  204. ;;
  205. Debian,*,*,*)
  206. printf "%s" "${baseurl}/${myghcver}/ghc-${myghcver}-${myarch}-deb9-linux.tar.xz"
  207. ;;
  208. Ubuntu,*,*,8.2.2)
  209. printf "%s" "${baseurl}/${myghcver}/ghc-${myghcver}-${myarch}-deb8-linux.tar.xz"
  210. ;;
  211. Ubuntu,*,*,*)
  212. printf "%s" "${baseurl}/${myghcver}/ghc-${myghcver}-${myarch}-deb9-linux.tar.xz"
  213. ;;
  214. *,*,*,8.2.2)
  215. printf "%s" "${baseurl}/${myghcver}/ghc-${myghcver}-${myarch}-deb8-linux.tar.xz"
  216. ;;
  217. *,*,*,*) # this is our best guess
  218. printf "%s" "${baseurl}/${myghcver}/ghc-${myghcver}-${myarch}-fedora27-linux.tar.xz"
  219. ;;
  220. esac
  221. unset myghcver myarch mydistro mydistrover baseurl
  222. }
  223. ## subcommand install ##
  224. install_ghc() {
  225. myghcver=$1
  226. downloader=curl
  227. downloader_opts="--fail -O"
  228. inst_location=${INSTALL_BASE}/ghc/${myghcver}
  229. target_location=${INSTALL_BASE}/bin
  230. download_url=$(get_download_url "${myghcver}")
  231. download_tarball_name=$(basename "${download_url}")
  232. [ -e "${target_location}" ] || mkdir "${target_location}"
  233. if [ -e "${inst_location}" ] ; then
  234. if ${FORCE} ; then
  235. echo "GHC already installed in ${inst_location}, overwriting!"
  236. else
  237. die "GHC already installed in ${inst_location}, use --force to overwrite"
  238. fi
  239. fi
  240. printf_green "Installing GHC for $(get_distro_name) on architecture $(get_arch)"
  241. tmp_dir=$(mktemp -d)
  242. [ -z "${tmp_dir}" ] && die "Failed to create temporary directory"
  243. (
  244. edo cd "${tmp_dir}"
  245. echov "Downloading ${download_url}"
  246. # shellcheck disable=SC2086
  247. edo ${downloader} ${downloader_opts} "${download_url}"
  248. edo tar -xf ghc-*-linux.tar.xz
  249. edo cd "ghc-${myghcver}"
  250. echov "Installing GHC into ${inst_location}"
  251. edo ./configure --prefix="${inst_location}"
  252. edo make install
  253. # clean up
  254. edo cd ..
  255. [ -e "${tmp_dir}/${download_tarball_name}" ] && rm "${tmp_dir}/${download_tarball_name}"
  256. [ -e "${tmp_dir}/ghc-${myghcver}" ] && rm -r "${tmp_dir}/ghc-${myghcver}"
  257. ) || {
  258. [ -e "${tmp_dir}/${download_tarball_name}" ] && rm "${tmp_dir}/${download_tarball_name}"
  259. [ -e "${tmp_dir}/ghc-${myghcver}" ] && rm -r "${tmp_dir}/ghc-${myghcver}"
  260. die "Failed to install"
  261. }
  262. for f in "${inst_location}"/bin/*-"${myghcver}" ; do
  263. [ -e "${f}" ] || die "Something went wrong, ${f} does not exist!"
  264. fn=$(basename "${f}")
  265. # shellcheck disable=SC2046
  266. edo ln $(echov "-v") -sf ../ghc/"${myghcver}/bin/${fn}" "${target_location}/${fn}"
  267. unset fn
  268. done
  269. # shellcheck disable=SC2046
  270. edo ln $(echov "-v") -sf ../ghc/"${myghcver}"/bin/runhaskell "${target_location}/runhaskell-${myghcver}"
  271. printf_green "Done installing, run \"ghci-${myghcver}\" or set up your current GHC via: ${SCRIPT} set-ghc ${myghcver}"
  272. unset myghcver downloader downloader_opts inst_location target_location f download_url download_tarball_name
  273. }
  274. ## subcommand set-ghc ##
  275. set_ghc() {
  276. myghcver=$1
  277. target_location=${INSTALL_BASE}/bin
  278. inst_location=${INSTALL_BASE}/ghc/${myghcver}
  279. [ -e "${inst_location}" ] || die "GHC ${myghcver} not installed yet, use: ${SCRIPT} install ${myghcver}"
  280. [ -e "${target_location}" ] || edo mkdir "${target_location}"
  281. printf_green "Setting GHC to ${myghcver}"
  282. for f in "${inst_location}"/bin/*-"${myghcver}" ; do
  283. [ -e "${f}" ] || die "Something went wrong, ${f} does not exist!"
  284. source_fn=$(basename "${f}")
  285. target_fn=$(echo "${source_fn}" | sed "s#-${myghcver}##")
  286. # shellcheck disable=SC2046
  287. edo ln $(echov "-v") -sf ../ghc/"${myghcver}/bin/${source_fn}" "${target_location}/${target_fn}"
  288. unset source_fn target_fn
  289. done
  290. # shellcheck disable=SC2046
  291. edo ln $(echov "-v") -sf runghc "${target_location}"/runhaskell
  292. printf_green "Done, make sure \"${target_location}\" is in your PATH!"
  293. unset myghcver target_location inst_location f
  294. }
  295. ## self-update subcommand ##
  296. self_update() {
  297. target_location=$1
  298. source_url="https://raw.githubusercontent.com/hasufell/ghcup/master/ghcup.sh"
  299. downloader=curl
  300. downloader_opts="--fail -O"
  301. [ -e "${target_location}" ] || die "Destination \"${target_location}\" does not exist, cannot update script"
  302. printf_green "Updating ${SCRIPT}"
  303. (
  304. edo cd "$(mktemp -d)"
  305. echov "Downloading ${source_url}"
  306. # shellcheck disable=SC2086
  307. edo ${downloader} ${downloader_opts} "${source_url}"
  308. edo mv ghcup.sh "${target_location}"/ghcup.sh
  309. edo chmod +x "${target_location}"/ghcup.sh
  310. )
  311. printf_green "Done, make sure \"${target_location}\" is in your PATH!"
  312. unset target_location source_url downloader downloader_opts
  313. }
  314. ## show subcommand ##
  315. show_ghc() {
  316. ghc_location=${INSTALL_BASE}/ghc
  317. current_ghc=$(show_ghc_installed)
  318. echo "Installed GHCs:"
  319. for i in "${ghc_location}"/* ; do
  320. [ -e "${i}" ] || die "Something went wrong, ${i} does not exist!"
  321. echo " $(basename "${i}")"
  322. done
  323. if [ -n "${current_ghc}" ] ; then
  324. echo
  325. echo "Current GHC"
  326. echo " ${current_ghc}"
  327. fi
  328. unset target_location i
  329. }
  330. show_ghc_installed() {
  331. target_location=${INSTALL_BASE}/bin
  332. real_ghc=$(realpath "${target_location}/ghc")
  333. if [ -e "${real_ghc}" ] ; then
  334. real_ghc="$(basename "${real_ghc}" | sed 's#ghc-##')"
  335. printf "%s" "${real_ghc}"
  336. fi
  337. unset target_location real_ghc
  338. }
  339. ## command line parsing and entry point ##
  340. # sanity checks
  341. if [ -z "$HOME" ] ; then
  342. die "HOME env not set, cannot operate"
  343. fi
  344. [ $# -lt 1 ] && usage
  345. while [ $# -gt 0 ] ; do
  346. case $1 in
  347. -v|--verbose)
  348. VERBOSE=true
  349. shift 1;;
  350. -V|--version)
  351. printf "%s" "${VERSION}"
  352. exit 0;;
  353. -h|--help)
  354. usage;;
  355. *) case $1 in
  356. install)
  357. shift 1
  358. while [ $# -gt 0 ] ; do
  359. case $1 in
  360. -h|--help) install_usage;;
  361. -f|--force) FORCE=true
  362. shift 1;;
  363. *) GHC_VER=$1
  364. break;;
  365. esac
  366. done
  367. [ "${GHC_VER}" ] || install_usage
  368. install_ghc "${GHC_VER}"
  369. break;;
  370. set)
  371. shift 1
  372. while [ $# -gt 0 ] ; do
  373. case $1 in
  374. -h|--help) set_usage;;
  375. *) GHC_VER=$1
  376. break;;
  377. esac
  378. done
  379. [ "${GHC_VER}" ] || set_usage
  380. set_ghc "${GHC_VER}"
  381. break;;
  382. self-update)
  383. shift 1
  384. while [ $# -gt 0 ] ; do
  385. case $1 in
  386. -h|--help) self_update_usage;;
  387. *) TARGET_LOCATION=$1
  388. break;;
  389. esac
  390. done
  391. if [ "${TARGET_LOCATION}" ] ; then
  392. self_update "${TARGET_LOCATION}"
  393. else
  394. self_update "${HOME}/.local/bin"
  395. fi
  396. break;;
  397. show)
  398. SHOW_INSTALLED=false
  399. shift 1
  400. while [ $# -gt 0 ] ; do
  401. case $1 in
  402. -h|--help) show_usage;;
  403. -i|--installed) SHOW_INSTALLED=true
  404. break;;
  405. *) show_usage;;
  406. esac
  407. done
  408. if ${SHOW_INSTALLED} ; then
  409. show_ghc_installed
  410. else
  411. show_ghc
  412. fi
  413. break;;
  414. *) usage;;
  415. esac
  416. break;;
  417. esac
  418. done