├── .gitignore ├── LICENSE ├── README.md ├── bin ├── build ├── compile ├── detect └── restore ├── profile.d └── buildpack.sh ├── src.sh └── src └── buildpack.sh /.gitignore: -------------------------------------------------------------------------------- 1 | lib 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2014-2015, Mietek Bak 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 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ------------------------------------------------------------------------------- 2 | 3 | This project is no longer maintained. 4 | 5 | ------------------------------------------------------------------------------- 6 | 7 | 8 | [Haskell on Heroku](https://haskellonheroku.com/) 9 | ================================================== 10 | 11 | Haskell on Heroku is a [Heroku](https://heroku.com/) buildpack for deploying Haskell apps. 12 | 13 | The buildpack uses [Halcyon](https://halcyon.sh/) to install apps and development tools, including [GHC](https://downloads.haskell.org/~ghc/latest/docs/html/users_guide/) and [Cabal](https://www.haskell.org/cabal/users-guide/). 14 | 15 | **Follow the [Haskell on Heroku tutorial](https://haskellonheroku.com/tutorial/) to get started.** 16 | 17 | 18 | Usage 19 | ----- 20 | 21 | Haskell on Heroku, like other [Heroku buildpacks](https://devcenter.heroku.com/articles/buildpacks), can be used when creating a new Heroku app: 22 | 23 | ``` 24 | $ heroku create -b https://github.com/mietek/haskell-on-heroku 25 | ``` 26 | 27 | Push the code to Heroku in order to deploy your app: 28 | 29 | ``` 30 | $ git push heroku master 31 | ``` 32 | 33 | 34 | ### Documentation 35 | 36 | - **Start with the [Haskell on Heroku tutorial](https://haskellonheroku.com/tutorial/) to learn how to develop a simple Haskell web app and deploy it to Heroku.** 37 | 38 | - Read the [Halcyon tutorial](https://halcyon.sh/tutorial/) to learn more about developing Haskell apps using Halcyon. 39 | 40 | - See the [Haskell on Heroku reference](https://haskellonheroku.com/reference/) for a list of buildpack-specific options. 41 | 42 | - Look for additional options in the [Halcyon reference](https://halcyon.sh/reference/). 43 | 44 | 45 | #### Internals 46 | 47 | Haskell on Heroku is written in [GNU _bash_](https://gnu.org/software/bash/), using the [_bashmenot_](https://bashmenot.mietek.io/) library. 48 | 49 | - Read the [Haskell on Heroku source code](https://github.com/mietek/haskell-on-heroku) to understand how it works. 50 | 51 | 52 | About 53 | ----- 54 | 55 | Made by [Miëtek Bak](https://mietek.io/). Published under the BSD license. Not affiliated with Heroku. 56 | -------------------------------------------------------------------------------- /bin/build: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eu 4 | 5 | export BUILDPACK_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )/.." && pwd -P ) 6 | 7 | source "${BUILDPACK_DIR}/src.sh" 8 | 9 | buildpack_build "$@" 10 | -------------------------------------------------------------------------------- /bin/compile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eu 4 | 5 | export BUILDPACK_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )/.." && pwd -P ) 6 | 7 | 8 | log_indent_label () { 9 | local label 10 | label="$1$( printf ' %.0s' {0..41} )" || true 11 | shift 12 | 13 | echo " ${label:0:41}" "$( echo -en '\033[1m' )${*}$( echo -en '\033[0m' )" >&2 14 | } 15 | 16 | 17 | set_url_config_var () { 18 | if [[ ! -f "$1/$2" ]]; then 19 | return 0 20 | fi 21 | 22 | local url 23 | url=$( <"$1/$2" ) || return 1 24 | export "$2"="${url}" 25 | 26 | # NOTE: The master branch is used by default, but the branch name is 27 | # omitted from the message. 28 | local base_url branch 29 | base_url="${url%#*}" 30 | branch="${url#*#}" 31 | if [[ "${branch}" == "${base_url}" ]]; then 32 | branch='' 33 | fi 34 | log_indent_label "$2:" "${base_url%.git}${branch:+#${branch}}" 35 | } 36 | 37 | 38 | set_config_vars () { 39 | unset GIT_DIR 40 | 41 | if [[ ! -d "$1" ]]; then 42 | return 0 43 | fi 44 | 45 | set_url_config_var "$1" 'BUILDPACK_URL' || return 1 46 | set_url_config_var "$1" 'HALCYON_URL' || return 1 47 | set_url_config_var "$1" 'BASHMENOT_URL' || return 1 48 | 49 | local ignored_pattern halcyon_pattern bashmenot_pattern secret_pattern 50 | ignored_pattern='BUILDPACK_INTERNAL_|BUILDPACK_URL|GIT_DIR|LD_LIBRARY_PATH|LD_PRELOAD|LIBRARY_PATH|PATH' 51 | halcyon_pattern='HALCYON_INTERNAL_|HALCYON_DIR|HALCYON_BASE|HALCYON_PREFIX|HALCYON_ROOT|HALCYON_CACHE|HALCYON_URL' 52 | bashmenot_pattern='BASHMENOT_INTERNAL_|BASHMENOT_DIR|BASHMENOT_URL' 53 | secret_pattern='DATABASE|KEY|PASSPHRASE|PASSWORD|POSTGRESQL|PRIVATE|SECRET' 54 | 55 | local vars 56 | vars=$( 57 | find "$1" -maxdepth 1 -type f | 58 | sed "s:$1/::" | 59 | sort 60 | ) || return 1 61 | if [[ -z "${vars}" ]]; then 62 | return 0 63 | fi 64 | 65 | local var 66 | while read -r var; do 67 | if grep -qE "(${ignored_pattern})" <<<"${var}"; then 68 | continue 69 | fi 70 | if grep -E "^HALCYON_" <<<"${var}" | grep -qE "(${halcyon_pattern})"; then 71 | continue 72 | fi 73 | if grep -E "^BASHMENOT_" <<<"${var}" | grep -qE "(${bashmenot_pattern})"; then 74 | continue 75 | fi 76 | 77 | local value 78 | value=$( <"$1/${var}" ) || return 1 79 | export "${var}"="${value}" 80 | 81 | if grep -qE "(${secret_pattern})" <<<"${var}"; then 82 | log_indent_label "${var}:" '(secret)' 83 | else 84 | log_indent_label "${var}:" "${value}" 85 | fi 86 | done <<<"${vars}" 87 | } 88 | 89 | 90 | install_buildpack () { 91 | echo >&2 92 | echo >&2 93 | echo '-----> Welcome to Haskell on Heroku' >&2 94 | 95 | if ! set_config_vars "${3:-/dev/null}"; then 96 | echo ' *** ERROR: Failed to set config vars' >&2 97 | return 1 98 | fi 99 | 100 | echo >&2 101 | printf -- '-----> Installing buildpack...' >&2 102 | 103 | local commit_hash 104 | if ! commit_hash=$( 105 | cd "${BUILDPACK_DIR}" && 106 | git log -n 1 --pretty='format:%h' 107 | ); then 108 | echo ' error' >&2 109 | return 1 110 | fi 111 | echo " done, ${commit_hash}" >&2 112 | 113 | BUILDPACK_NO_SELF_UPDATE=1 \ 114 | source "${BUILDPACK_DIR}/src.sh" || return 1 115 | } 116 | 117 | 118 | if ! install_buildpack "$@"; then 119 | echo ' *** ERROR: Failed to install buildpack' >&2 120 | exit 1 121 | fi 122 | 123 | buildpack_compile "$@" 124 | -------------------------------------------------------------------------------- /bin/detect: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | echo 'Haskell' 4 | -------------------------------------------------------------------------------- /bin/restore: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eu 4 | 5 | export BUILDPACK_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )/.." && pwd -P ) 6 | 7 | source "${BUILDPACK_DIR}/src.sh" 8 | 9 | buildpack_restore "$@" 10 | -------------------------------------------------------------------------------- /profile.d/buildpack.sh: -------------------------------------------------------------------------------- 1 | export BUILDPACK_DIR='/app/.buildpack' 2 | 3 | 4 | # NOTE: Self-updating is disabled on worker dynos. 5 | if [[ "${DYNO%.*}" != 'run' ]]; then 6 | export BUILDPACK_NO_SELF_UPDATE=1 7 | export HALCYON_NO_SELF_UPDATE=1 8 | export BASHMENOT_NO_SELF_UPDATE=1 9 | fi 10 | 11 | 12 | if [[ -n "${BUILDPACK_SSH_PRIVATE_KEY}" ]]; then 13 | mkdir -p '/app/.ssh' 14 | 15 | echo "${BUILDPACK_SSH_PRIVATE_KEY}" >'/app/.ssh/id_rsa' 16 | unset BUILDPACK_SSH_PRIVATE_KEY 17 | 18 | chmod 700 '/app/.ssh' 19 | chmod 600 '/app/.ssh/id_rsa' 20 | 21 | cat <<-EOF >'/app/.ssh/config' 22 | Host * 23 | StrictHostKeyChecking no 24 | UserKnownHostsFile=/dev/null 25 | EOF 26 | fi 27 | 28 | 29 | if ! (( ${BUILDPACK_INTERNAL_PATHS:-0} )); then 30 | export BUILDPACK_INTERNAL_PATHS=1 31 | 32 | export PATH="${BUILDPACK_DIR}/bin:${PATH:-}" 33 | 34 | eval "$( HALCYON_NO_SELF_UPDATE=1 "${BUILDPACK_DIR}/lib/halcyon/halcyon" paths )" 35 | fi 36 | -------------------------------------------------------------------------------- /src.sh: -------------------------------------------------------------------------------- 1 | unset POSIXLY_CORRECT 2 | 3 | set -o pipefail 4 | 5 | export BUILDPACK_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd -P ) 6 | 7 | 8 | install_halcyon () { 9 | if [[ -d "${BUILDPACK_DIR}/lib/halcyon" ]]; then 10 | eval "$( HALCYON_NO_SELF_UPDATE="${BUILDPACK_NO_SELF_UPDATE:-0}" "${BUILDPACK_DIR}/lib/halcyon/halcyon" paths )" || return 1 11 | BASHMENOT_NO_SELF_UPDATE=1 \ 12 | source "${BUILDPACK_DIR}/lib/halcyon/lib/bashmenot/src.sh" || return 1 13 | return 0 14 | fi 15 | 16 | local url base_url branch 17 | url="${HALCYON_URL:-https://github.com/mietek/halcyon}" 18 | base_url="${url%#*}" 19 | branch="${url#*#}" 20 | if [[ "${branch}" == "${base_url}" ]]; then 21 | branch='master' 22 | fi 23 | 24 | printf -- '-----> Installing Halcyon...' >&2 25 | 26 | local commit_hash 27 | if ! commit_hash=$( 28 | git clone -q "${base_url}" "${BUILDPACK_DIR}/lib/halcyon" >'/dev/null' 2>&1 && 29 | cd "${BUILDPACK_DIR}/lib/halcyon" && 30 | git checkout -q "${branch}" >'/dev/null' 2>&1 && 31 | git log -n 1 --pretty='format:%h' 32 | ); then 33 | echo ' error' >&2 34 | return 1 35 | fi 36 | echo " done, ${commit_hash}" >&2 37 | 38 | eval "$( HALCYON_NO_SELF_UPDATE=1 "${BUILDPACK_DIR}/lib/halcyon/halcyon" paths )" || return 1 39 | BASHMENOT_NO_SELF_UPDATE=1 \ 40 | source "${BUILDPACK_DIR}/lib/halcyon/lib/bashmenot/src.sh" || return 1 41 | } 42 | 43 | 44 | if ! install_halcyon; then 45 | echo ' *** ERROR: Failed to install Halcyon' >&2 46 | exit 1 47 | fi 48 | 49 | source "${BUILDPACK_DIR}/src/buildpack.sh" 50 | 51 | 52 | buildpack_self_update () { 53 | if (( ${BUILDPACK_NO_SELF_UPDATE:-0} )) || 54 | [[ ! -d "${BUILDPACK_DIR}/.git" ]] 55 | then 56 | return 0 57 | fi 58 | 59 | local now candidate_time 60 | now=$( get_current_time ) 61 | if candidate_time=$( get_modification_time "${BUILDPACK_DIR}" ) && 62 | (( candidate_time + 60 >= now )) 63 | then 64 | return 0 65 | fi 66 | 67 | local url 68 | url="${BUILDPACK_URL:-https://github.com/mietek/haskell-on-heroku}" 69 | 70 | log_begin 'Self-updating buildpack...' 71 | 72 | local commit_hash 73 | if ! commit_hash=$( git_update_into "${url}" "${BUILDPACK_DIR}" ); then 74 | log_end 'error' 75 | return 0 76 | fi 77 | log_end "done, ${commit_hash}" 78 | 79 | touch "${BUILDPACK_DIR}" || return 1 80 | 81 | BUILDPACK_NO_SELF_UPDATE=1 \ 82 | source "${BUILDPACK_DIR}/src.sh" 83 | } 84 | 85 | 86 | if ! buildpack_self_update; then 87 | log_error 'Failed to self-update buildpack' 88 | exit 1 89 | fi 90 | -------------------------------------------------------------------------------- /src/buildpack.sh: -------------------------------------------------------------------------------- 1 | private_storage () { 2 | [[ -n "${HALCYON_AWS_ACCESS_KEY_ID:+_}" 3 | && -n "${HALCYON_AWS_SECRET_ACCESS_KEY:+_}" 4 | && -n "${HALCYON_S3_BUCKET:+_}" ]] || return 1 5 | } 6 | 7 | 8 | expect_private_storage () { 9 | if ! private_storage; then 10 | log_error 'Expected private storage' 11 | log 12 | log_indent 'To set up private storage:' 13 | log_indent '$ heroku config:set HALCYON_AWS_ACCESS_KEY_ID=...' 14 | log_indent '$ heroku config:set HALCYON_AWS_SECRET_ACCESS_KEY=...' 15 | log_indent '$ heroku config:set HALCYON_S3_BUCKET=...' 16 | log 17 | return 1 18 | fi 19 | } 20 | 21 | 22 | buildpack_compile () { 23 | expect_vars BUILDPACK_DIR 24 | 25 | expect_existing "${BUILDPACK_DIR}" || return 1 26 | 27 | local build_dir cache_dir 28 | expect_args build_dir cache_dir -- "$@" 29 | 30 | expect_existing "${build_dir}" || return 1 31 | expect_no_existing "${build_dir}/.buildpack" || return 1 32 | 33 | local root_dir 34 | root_dir=$( get_tmp_dir 'root' ) || return 1 35 | 36 | # NOTE: Files copied into build_dir will be present in /app on a 37 | # dyno. This includes files which should not contribute to 38 | # source_hash, hence the need to archive and restore the source dir. 39 | if ! create_archive "${build_dir}" '/tmp/source.tar.gz' 2>'/dev/null'; then 40 | log_error 'Failed to prepare source directory' 41 | return 1 42 | fi 43 | 44 | local label executable 45 | if ! label=$( 46 | HALCYON_NO_SELF_UPDATE=1 \ 47 | HALCYON_INTERNAL_NO_COPY_LOCAL_SOURCE=1 \ 48 | halcyon label "${build_dir}" 2>'/dev/null' 49 | ) || ! executable=$( 50 | HALCYON_NO_SELF_UPDATE=1 \ 51 | HALCYON_INTERNAL_NO_COPY_LOCAL_SOURCE=1 \ 52 | halcyon executable "${build_dir}" 2>'/dev/null' 53 | ); then 54 | log_error 'Failed to detect app' 55 | return 1 56 | fi 57 | 58 | # NOTE: Returns 2 if build is needed, if NO_BUILD_DEPENDENCIES is 1. 59 | local status 60 | status=0 61 | HALCYON_NO_SELF_UPDATE=1 \ 62 | HALCYON_BASE='/app' \ 63 | HALCYON_PREFIX='/app' \ 64 | HALCYON_ROOT="${root_dir}" \ 65 | HALCYON_NO_BUILD_DEPENDENCIES="${HALCYON_NO_BUILD_DEPENDENCIES:-1}" \ 66 | HALCYON_CACHE="${cache_dir}" \ 67 | HALCYON_INTERNAL_NO_ANNOUNCE_INSTALL=1 \ 68 | HALCYON_INTERNAL_NO_CLEANUP=1 \ 69 | HALCYON_INTERNAL_NO_COPY_LOCAL_SOURCE=1 \ 70 | halcyon install "${build_dir}" || status="$?" 71 | 72 | if ! copy_dir_over "${BUILDPACK_DIR}" "${build_dir}/.buildpack" || 73 | ! copy_file "${BUILDPACK_DIR}/profile.d/buildpack.sh" \ 74 | "${build_dir}/.profile.d/buildpack.sh" || 75 | ! copy_file '/tmp/source.tar.gz' "${build_dir}/.buildpack/source.tar.gz" 76 | then 77 | log_error 'Failed to prepare slug directory' 78 | return 1 79 | fi 80 | 81 | if [[ ! -f "${build_dir}/.ghci" ]]; then 82 | if ! echo ':set prompt "\ESC[32;1m\x03BB \ESC[0m"' >"${build_dir}/.ghci"; then 83 | log_error 'Failed to set custom GHCi prompt' 84 | return 1 85 | fi 86 | fi 87 | 88 | case "${status}" in 89 | '0') 90 | if ! copy_dir_into "${root_dir}/app" "${build_dir}"; then 91 | log_error 'Failed to copy app to slug directory' 92 | return 1 93 | fi 94 | 95 | if (( ${HALCYON_KEEP_DEPENDENCIES:-0} )); then 96 | if ! copy_dir_over '/app/ghc' "${build_dir}/ghc" || 97 | ! copy_dir_over '/app/cabal' "${build_dir}/cabal" || 98 | ! copy_dir_over '/app/sandbox' "${build_dir}/sandbox" 99 | then 100 | log_error 'Failed to copy dependencies to slug directory' 101 | return 1 102 | fi 103 | fi 104 | 105 | if [[ ! -f "${build_dir}/Procfile" ]]; then 106 | if ! echo "web: bin/${executable}" >"${build_dir}/Procfile"; then 107 | log_error 'Failed to generate Procfile' 108 | return 1 109 | fi 110 | fi 111 | 112 | log 113 | log_label 'App deployed:' "${label}" 114 | log 115 | log_indent 'To see the app, spin up at least one web dyno:' 116 | log_indent '$ heroku ps:scale web=1' 117 | log_indent '$ heroku open' 118 | log 119 | log_indent 'To run GHCi, use a one-off dyno:' 120 | log_indent '$ heroku run bash' 121 | log_indent '~ $ restore' 122 | log_indent '~ $ cabal repl' 123 | log 124 | log 125 | ;; 126 | '2') 127 | log_error 'Failed to deploy app' 128 | log_error 'Deploying buildpack only' 129 | log 130 | if ! private_storage; then 131 | log_indent 'First, set up private storage:' 132 | log_indent '$ heroku config:set HALCYON_AWS_ACCESS_KEY_ID=...' 133 | log_indent '$ heroku config:set HALCYON_AWS_SECRET_ACCESS_KEY=...' 134 | log_indent '$ heroku config:set HALCYON_S3_BUCKET=...' 135 | log 136 | fi 137 | log_indent 'To continue, build the app on a one-off PX dyno:' 138 | log_indent '$ heroku run -s PX build' 139 | log 140 | log_indent 'Next, deploy the app:' 141 | log_indent '$ git commit --amend --no-edit' 142 | log_indent '$ git push -f heroku master' 143 | log 144 | log 145 | ;; 146 | *) 147 | log_error 'Failed to deploy app' 148 | return 1 149 | esac 150 | } 151 | 152 | 153 | buildpack_install () { 154 | expect_vars BUILDPACK_DIR 155 | 156 | expect_existing "${BUILDPACK_DIR}" || return 1 157 | 158 | local source_dir 159 | source_dir=$( get_tmp_dir 'buildpack-source' ) || return 1 160 | 161 | if ! extract_archive_over "${BUILDPACK_DIR}/source.tar.gz" "${source_dir}" 2>'/dev/null'; then 162 | log_error 'Failed to restore source directory' 163 | return 1 164 | fi 165 | 166 | local label 167 | if ! label=$( 168 | HALCYON_NO_SELF_UPDATE=1 \ 169 | HALCYON_INTERNAL_NO_COPY_LOCAL_SOURCE=1 \ 170 | halcyon label "${source_dir}" 2>'/dev/null' 171 | ); then 172 | log_error 'Failed to detect app' 173 | return 1 174 | fi 175 | 176 | HALCYON_NO_SELF_UPDATE=1 \ 177 | HALCYON_BASE='/app' \ 178 | HALCYON_PREFIX='/app' \ 179 | HALCYON_INTERNAL_NO_ANNOUNCE_INSTALL=1 \ 180 | HALCYON_INTERNAL_NO_CLEANUP=1 \ 181 | HALCYON_INTERNAL_NO_COPY_LOCAL_SOURCE=1 \ 182 | halcyon install "${source_dir}" "$@" || return 1 183 | 184 | echo "${label}" 185 | } 186 | 187 | 188 | buildpack_build () { 189 | expect_private_storage || return 1 190 | 191 | local label 192 | if ! label=$( buildpack_install "$@" ); then 193 | log_error 'Failed to build app' 194 | return 1 195 | fi 196 | 197 | log 198 | log_label 'App built:' "${label}" 199 | log 200 | log_indent 'To deploy the app:' 201 | log_indent '$ git commit --amend --no-edit' 202 | log_indent '$ git push -f heroku master' 203 | log 204 | log 205 | } 206 | 207 | 208 | buildpack_restore () { 209 | local label 210 | if ! label=$( 211 | HALCYON_KEEP_DEPENDENCIES=1 \ 212 | buildpack_install "$@" 213 | ); then 214 | log_error 'Failed to restore app' 215 | return 1 216 | fi 217 | 218 | log 219 | log_label 'App restored:' "${label}" 220 | log 221 | log_indent 'To run GHCi:' 222 | log_indent '$ cabal repl' 223 | log 224 | log 225 | } 226 | --------------------------------------------------------------------------------