diff --git a/.gitignore b/.gitignore index 97cc352475..9a9e3dd5e9 100644 --- a/.gitignore +++ b/.gitignore @@ -45,3 +45,6 @@ test/testdata/**/hie.yaml # nix result result-doc + +out/ +store/ diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 6ef38692a3..8fe44a866e 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,6 +1,7 @@ stages: - build - tar + - test # Used for ci setup in the gitlab mirror of the project: # https://gitlab.haskell.org/haskell/haskell-language-server/-/pipelines @@ -10,7 +11,7 @@ variables: CABAL_INSTALL_VERSION: 3.6.2.0 -.default_matrix: &default_matrix +.windows_matrix: &windows_matrix matrix: - GHC_VERSION: 8.8.4 CABAL_PROJECT: cabal.project @@ -21,27 +22,6 @@ variables: - GHC_VERSION: 9.2.1 CABAL_PROJECT: cabal-ghc921.project -# ghc-9.2.1 is broken for bsd -.freebsd_matrix: &freebsd_matrix - matrix: - - GHC_VERSION: 8.8.4 - CABAL_PROJECT: cabal.project - - GHC_VERSION: 8.10.7 - CABAL_PROJECT: cabal.project - - GHC_VERSION: 9.0.2 - CABAL_PROJECT: cabal-ghc90.project - -.m1_matrix: &m1_matrix - matrix: - - GHC_VERSION: 8.10.7 - CABAL_PROJECT: cabal.project - -.arm_matrix: &arm_matrix - matrix: - - GHC_VERSION: 8.10.7 - CABAL_PROJECT: cabal.project - - workflow: rules: - if: $CI_COMMIT_TAG @@ -66,101 +46,304 @@ workflow: script: - bash .gitlab/ci.sh -build-aarch64-linux-deb10: - extends: .build +.test: + stage: test + script: + - bash .gitlab/test.sh + +.aarch64-linux: tags: - aarch64-linux image: "registry.gitlab.haskell.org/ghc/ci-images/aarch64-linux-deb10:$DOCKER_REV" - parallel: *arm_matrix + +.armv7-linux: + tags: + - armv7-linux + image: "registry.gitlab.haskell.org/ghc/ci-images/armv7-linux-deb10:$DOCKER_REV" + +.x86_64-linux-deb10: + tags: + - x86_64-linux + image: "registry.gitlab.haskell.org/ghc/ci-images/x86_64-linux-deb10:$DOCKER_REV" + +.x86_64-linux-deb9: + tags: + - x86_64-linux + image: "registry.gitlab.haskell.org/ghc/ci-images/x86_64-linux-deb9:$DOCKER_REV" + +.x86_64-linux-centos7: + tags: + - x86_64-linux + image: "registry.gitlab.haskell.org/ghc/ci-images/x86_64-linux-centos7:$DOCKER_REV" + +.x86_64-linux-fedora27: + tags: + - x86_64-linux + image: "registry.gitlab.haskell.org/ghc/ci-images/x86_64-linux-fedora27:$DOCKER_REV" + +.x86_64-linux-alpine: + tags: + - x86_64-linux + image: "registry.gitlab.haskell.org/ghc/ci-images/x86_64-linux-alpine3_12:$DOCKER_REV" + + +###################### +# aarch64 linux deb10 +###################### + +build-aarch64-linux-deb10: + extends: + - .build + - .aarch64-linux + before_script: + - sudo apt update + - sudo apt install -y patchelf tree variables: ADD_CABAL_ARGS: "" tar-aarch64-linux-deb10: - extends: .artifacts + extends: + - .artifacts + - .aarch64-linux stage: tar needs: ["build-aarch64-linux-deb10"] - tags: - - aarch64-linux - image: "registry.gitlab.haskell.org/ghc/ci-images/aarch64-linux-deb10:$DOCKER_REV" script: - ./.gitlab/tar.sh variables: - TARBALL_ARCHIVE_SUFFIX: Linux-aarch64 + TARBALL_ARCHIVE_SUFFIX: aarch64-deb10-linux TARBALL_EXT: tar.xz +test-aarch64-linux-deb10: + extends: + - .test + - .aarch64-linux + needs: ["tar-aarch64-linux-deb10"] + before_script: + - sudo apt update + - sudo apt install -y tree + + +###################### +# armv7 linux deb10 +###################### + build-armv7-linux-deb10: - extends: .build - tags: - - armv7-linux - image: "registry.gitlab.haskell.org/ghc/ci-images/armv7-linux-deb10:$DOCKER_REV" - parallel: *arm_matrix + extends: + - .build + - .armv7-linux + before_script: + - sudo apt update + - sudo apt install -y patchelf tree variables: ADD_CABAL_ARGS: "" tar-armv7-linux-deb10: - extends: .artifacts + extends: + - .artifacts + - .armv7-linux stage: tar needs: ["build-armv7-linux-deb10"] - tags: - - armv7-linux - image: "registry.gitlab.haskell.org/ghc/ci-images/armv7-linux-deb10:$DOCKER_REV" script: - ./.gitlab/tar.sh variables: - TARBALL_ARCHIVE_SUFFIX: Linux-armv7 + TARBALL_ARCHIVE_SUFFIX: armv7-deb10-linux TARBALL_EXT: tar.xz -build-x86_64-linux: - extends: .build - tags: - - x86_64-linux - image: "registry.gitlab.haskell.org/ghc/ci-images/x86_64-linux-deb10:$DOCKER_REV" - parallel: *default_matrix +test-armv7-linux-deb10: + extends: + - .test + - .armv7-linux + needs: ["tar-armv7-linux-deb10"] + before_script: + - sudo apt update + - sudo apt install -y tree + + +###################### +# x86_64 linux deb10 +###################### + +build-x86_64-linux-deb10: + extends: + - .build + - .x86_64-linux-deb10 + before_script: + - sudo apt update + - sudo apt install -y patchelf tree variables: ADD_CABAL_ARGS: "--enable-split-sections" -tar-x86_64-linux: - extends: .artifacts +tar-x86_64-linux-deb10: + extends: + - .artifacts + - .x86_64-linux-deb10 stage: tar - needs: ["build-x86_64-linux"] - tags: - - x86_64-linux - image: "registry.gitlab.haskell.org/ghc/ci-images/x86_64-linux-deb10:$DOCKER_REV" + needs: ["build-x86_64-linux-deb10"] + script: + - ./.gitlab/tar.sh + variables: + TARBALL_ARCHIVE_SUFFIX: x86_64-deb10-linux + TARBALL_EXT: tar.xz + +test-x86_64-linux-deb10: + extends: + - .test + - .x86_64-linux-deb10 + needs: ["tar-x86_64-linux-deb10"] + before_script: + - sudo apt update + - sudo apt install -y tree + +###################### +# x86_64 linux deb9 +###################### + +build-x86_64-linux-deb9: + extends: + - .build + - .x86_64-linux-deb9 + before_script: + - sudo apt update + - sudo apt install -y patchelf tree + variables: + ADD_CABAL_ARGS: "--enable-split-sections" + +tar-x86_64-linux-deb9: + extends: + - .artifacts + - .x86_64-linux-deb9 + stage: tar + needs: ["build-x86_64-linux-deb9"] + script: + - ./.gitlab/tar.sh + variables: + TARBALL_ARCHIVE_SUFFIX: x86_64-deb9-linux + TARBALL_EXT: tar.xz + +test-x86_64-linux-deb9: + extends: + - .test + - .x86_64-linux-deb9 + needs: ["tar-x86_64-linux-deb9"] + before_script: + - sudo apt update + - sudo apt install -y tree + +###################### +# x86_64 linux centos7 +###################### + +build-x86_64-linux-centos7: + extends: + - .build + - .x86_64-linux-centos7 + before_script: + - sudo yum install -y epel-release + - sudo yum install -y patchelf tree + variables: + ADD_CABAL_ARGS: "--enable-split-sections" + +tar-x86_64-linux-centos7: + extends: + - .artifacts + - .x86_64-linux-centos7 + stage: tar + needs: ["build-x86_64-linux-centos7"] + script: + - ./.gitlab/tar.sh + variables: + TARBALL_ARCHIVE_SUFFIX: x86_64-centos7-linux + TARBALL_EXT: tar.xz + +test-x86_64-linux-centos7: + extends: + - .test + - .x86_64-linux-centos7 + needs: ["tar-x86_64-linux-centos7"] + before_script: + - sudo yum install -y tree + +###################### +# x86_64 linux fedora27 +###################### + +build-x86_64-linux-fedora27: + extends: + - .build + - .x86_64-linux-fedora27 + before_script: + - sudo dnf install -y patchelf tree + variables: + ADD_CABAL_ARGS: "--enable-split-sections" + +tar-x86_64-linux-fedora27: + extends: + - .artifacts + - .x86_64-linux-fedora27 + stage: tar + needs: ["build-x86_64-linux-fedora27"] script: - ./.gitlab/tar.sh variables: - TARBALL_ARCHIVE_SUFFIX: Linux-x86_64 + TARBALL_ARCHIVE_SUFFIX: x86_64-fedora27-linux TARBALL_EXT: tar.xz +test-x86_64-linux-fedora27: + extends: + - .test + - .x86_64-linux-fedora27 + needs: ["tar-x86_64-linux-fedora27"] + before_script: + - sudo dnf install -y tree + + +###################### +# x86_64 linux alpine +###################### + build-x86_64-linux-alpine: - extends: .build - tags: - - x86_64-linux - image: "registry.gitlab.haskell.org/ghc/ci-images/x86_64-linux-alpine3_12:$DOCKER_REV" + extends: + - .build + - .x86_64-linux-alpine before_script: - - sudo apk add --no-cache zlib zlib-dev zlib-static bzip2 bzip2-dev bzip2-static gmp gmp-dev xz xz-dev ncurses-static - parallel: *default_matrix + - sudo apk add --no-cache tar zlib zlib-dev zlib-static bzip2 bzip2-dev bzip2-static gmp gmp-dev xz xz-dev ncurses-static patchelf findutils tree variables: ADD_CABAL_ARGS: "--enable-split-sections --enable-executable-static" tar-x86_64-linux-alpine: - extends: .artifacts + extends: + - .artifacts + - .x86_64-linux-alpine stage: tar needs: ["build-x86_64-linux-alpine"] - tags: - - x86_64-linux - image: "registry.gitlab.haskell.org/ghc/ci-images/x86_64-linux-alpine3_12:$DOCKER_REV" + before_script: + - sudo apk add --no-cache tar zlib zlib-dev zlib-static bzip2 bzip2-dev bzip2-static gmp gmp-dev xz xz-dev ncurses-static script: - ./.gitlab/tar.sh variables: - TARBALL_ARCHIVE_SUFFIX: Linux-alpine-x86_64 + TARBALL_ARCHIVE_SUFFIX: x86_64-alpine3.12-linux TARBALL_EXT: tar.xz +test-x86_64-linux-alpine: + extends: + - .test + - .x86_64-linux-alpine + needs: ["tar-x86_64-linux-alpine"] + before_script: + - sudo apk add --no-cache tar zlib zlib-dev zlib-static bzip2 bzip2-dev bzip2-static gmp gmp-dev xz xz-dev ncurses-static tree + + +###################### +# x86_64 freebsd 12 +###################### + build-x86_64-freebsd12: extends: .build + before_script: + - sudo pkg update + - sudo pkg install --yes patchelf gmake tree binutils tags: - x86_64-freebsd12 - parallel: *freebsd_matrix variables: ADD_CABAL_ARGS: "--enable-split-sections -j1" @@ -173,17 +356,30 @@ tar-x86_64-freebsd12: script: - ./.gitlab/tar.sh variables: - TARBALL_ARCHIVE_SUFFIX: FreeBSD12-x86_64 + TARBALL_ARCHIVE_SUFFIX: x86_64-unknown-freebsd12 TARBALL_EXT: tar.xz +test-x86_64-freebsd12: + extends: .test + needs: ["tar-x86_64-freebsd12"] + tags: + - x86_64-freebsd12 + before_script: + - sudo pkg update + - sudo pkg install --yes patchelf gmake tree binutils + + +###################### +# x86_64 freebsd 13 +###################### + build-x86_64-freebsd13: extends: .build tags: - x86_64-freebsd13 - parallel: *freebsd_matrix before_script: - sudo pkg update - - sudo pkg install --yes compat12x-amd64 + - sudo pkg install --yes compat12x-amd64 patchelf gmake tree binutils - sudo ln -s libncurses.so.6 /usr/local/lib/libncurses.so.6.2 variables: ADD_CABAL_ARGS: "--enable-split-sections -j1" @@ -197,16 +393,37 @@ tar-x86_64-freebsd13: script: - ./.gitlab/tar.sh variables: - TARBALL_ARCHIVE_SUFFIX: FreeBSD13-x86_64 + TARBALL_ARCHIVE_SUFFIX: x86_64-unknown-freebsd13 TARBALL_EXT: tar.xz +test-x86_64-freebsd13: + extends: .test + needs: ["tar-x86_64-freebsd13"] + tags: + - x86_64-freebsd13 + before_script: + - sudo pkg update + - sudo pkg install --yes compat12x-amd64 gmake tree binutils + - sudo ln -s libncurses.so.6 /usr/local/lib/libncurses.so.6.2 + + +###################### +# x86_64 darwin +###################### + build-x86_64-darwin: extends: .build tags: - x86_64-darwin - parallel: *default_matrix variables: ADD_CABAL_ARGS: "" + before_script: + - /bin/bash ./.gitlab/brew.sh autoconf automake coreutils make tree + script: | + export PATH="$CI_PROJECT_DIR/.brew/bin:$CI_PROJECT_DIR/.brew/sbin:$PATH" + /bin/bash ./.gitlab/ci.sh + after_script: + - rm -Rf /private/tmp/.brew_tmp tar-x86_64-darwin: extends: .artifacts @@ -214,37 +431,44 @@ tar-x86_64-darwin: needs: ["build-x86_64-darwin"] tags: - x86_64-darwin - script: - - ./.gitlab/tar.sh + before_script: + - /bin/bash ./.gitlab/brew.sh autoconf automake coreutils make tree + script: | + export PATH="$CI_PROJECT_DIR/.brew/bin:$CI_PROJECT_DIR/.brew/sbin:$PATH" + /bin/bash ./.gitlab/tar.sh + after_script: + - rm -Rf /private/tmp/.brew_tmp variables: - TARBALL_ARCHIVE_SUFFIX: macOS-x86_64 + TARBALL_ARCHIVE_SUFFIX: x86_64-apple-darwin TARBALL_EXT: tar.xz +test-x86_64-darwin: + extends: .test + needs: ["tar-x86_64-darwin"] + tags: + - x86_64-darwin + before_script: + - /bin/bash ./.gitlab/brew.sh make tree + script: | + export PATH="$CI_PROJECT_DIR/.brew/bin:$CI_PROJECT_DIR/.brew/sbin:$PATH" + /bin/bash .gitlab/test.sh + after_script: + - rm -Rf /private/tmp/.brew_tmp + + +###################### +# aarch64 darwin +###################### + build-aarch64-darwin: extends: .artifacts:short stage: build tags: - aarch64-darwin-m1 before_script: - # Install brew locally in the project dir. Packages will also be installed here. - - '[ -e "$CI_PROJECT_DIR/.brew" ] || git clone --depth=1 https://github.com/Homebrew/brew $CI_PROJECT_DIR/.brew' - - export PATH="$CI_PROJECT_DIR/.brew/bin:$CI_PROJECT_DIR/.brew/sbin:$PATH" - - # otherwise we seem to get intel binaries - export HOMEBREW_CHANGE_ARCH_TO_ARM=1 - - # make sure to not pollute the machine with temp files etc - - mkdir -p $CI_PROJECT_DIR/.brew_cache - - export HOMEBREW_CACHE=$CI_PROJECT_DIR/.brew_cache - - mkdir -p $CI_PROJECT_DIR/.brew_logs - - export HOMEBREW_LOGS=$CI_PROJECT_DIR/.brew_logs - - mkdir -p /private/tmp/.brew_tmp - - export HOMEBREW_TEMP=/private/tmp/.brew_tmp - - # update and install packages - - brew update - - brew install llvm - - brew install autoconf automake coreutils + - arch -arm64 /bin/bash ./.gitlab/brew.sh llvm autoconf automake coreutils make tree + # C_INCLUDE_PATH: https://gitlab.haskell.org/ghc/ghc/-/issues/20592 script: | export PATH="$CI_PROJECT_DIR/.brew/opt/llvm/bin:$CI_PROJECT_DIR/.brew/bin:$CI_PROJECT_DIR/.brew/sbin:$PATH" export CC=$CI_PROJECT_DIR/.brew/opt/llvm/bin/clang @@ -252,13 +476,13 @@ build-aarch64-darwin: export LD=ld export AR=$CI_PROJECT_DIR/.brew/opt/llvm/bin/llvm-ar export RANLIB=$CI_PROJECT_DIR/.brew/opt/llvm/bin/llvm-ranlib + export C_INCLUDE_PATH="`xcrun --show-sdk-path`/usr/include/ffi" arch -arm64 /bin/bash ./.gitlab/ci.sh after_script: - rm -Rf /private/tmp/.brew_tmp variables: MACOSX_DEPLOYMENT_TARGET: "10.7" ADD_CABAL_ARGS: "" - parallel: *m1_matrix tar-aarch64-darwin: extends: .artifacts @@ -269,14 +493,35 @@ tar-aarch64-darwin: script: - arch -arm64 /bin/bash ./.gitlab/tar.sh variables: - TARBALL_ARCHIVE_SUFFIX: macOS-aarch64 + TARBALL_ARCHIVE_SUFFIX: aarch64-apple-darwin TARBALL_EXT: tar.xz +test-aarch64-darwin: + extends: .test + needs: ["tar-aarch64-darwin"] + tags: + - aarch64-darwin-m1 + before_script: + - export HOMEBREW_CHANGE_ARCH_TO_ARM=1 + - arch -arm64 /bin/bash ./.gitlab/brew.sh make tree + # C_INCLUDE_PATH: https://gitlab.haskell.org/ghc/ghc/-/issues/20592 + script: | + export PATH="$CI_PROJECT_DIR/.brew/opt/llvm/bin:$CI_PROJECT_DIR/.brew/bin:$CI_PROJECT_DIR/.brew/sbin:$PATH" + export C_INCLUDE_PATH="`xcrun --show-sdk-path`/usr/include/ffi" + arch -arm64 /bin/bash ./.gitlab/test.sh + after_script: + - rm -Rf /private/tmp/.brew_tmp + + +###################### +# x86_64 windows +###################### + build-x86_64-windows: extends: .build tags: - new-x86_64-windows - parallel: *default_matrix + parallel: *windows_matrix script: - $env:CHERE_INVOKING = "yes" - bash '-lc' "ADD_CABAL_ARGS=$env:ADD_CABAL_ARGS GHC_VERSION=$env:GHC_VERSION CABAL_INSTALL_VERSION=$CABAL_INSTALL_VERSION .gitlab/ci.sh" @@ -293,5 +538,5 @@ tar-x86_64-windows: - $env:CHERE_INVOKING = "yes" - bash '-lc' "TARBALL_ARCHIVE_SUFFIX=$env:TARBALL_ARCHIVE_SUFFIX TARBALL_EXT=$env:TARBALL_EXT .gitlab/tar.sh" variables: - TARBALL_ARCHIVE_SUFFIX: Windows-x86_64 + TARBALL_ARCHIVE_SUFFIX: x86_64-unknown-mingw32 TARBALL_EXT: zip diff --git a/.gitlab/brew.sh b/.gitlab/brew.sh new file mode 100644 index 0000000000..de769632e6 --- /dev/null +++ b/.gitlab/brew.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash + +set -Eeuxo pipefail + +# Install brew locally in the project dir. Packages will also be installed here. +[ -e "$CI_PROJECT_DIR/.brew" ] || git clone --depth=1 https://github.com/Homebrew/brew $CI_PROJECT_DIR/.brew +export PATH="$CI_PROJECT_DIR/.brew/bin:$CI_PROJECT_DIR/.brew/sbin:$PATH" + +# make sure to not pollute the machine with temp files etc +mkdir -p $CI_PROJECT_DIR/.brew_cache +export HOMEBREW_CACHE=$CI_PROJECT_DIR/.brew_cache +mkdir -p $CI_PROJECT_DIR/.brew_logs +export HOMEBREW_LOGS=$CI_PROJECT_DIR/.brew_logs +mkdir -p /private/tmp/.brew_tmp +export HOMEBREW_TEMP=/private/tmp/.brew_tmp + +# update and install packages +brew update +brew install ${1+"$@"} diff --git a/.gitlab/ci.sh b/.gitlab/ci.sh index 5267bc8c8f..58a66ff6d5 100755 --- a/.gitlab/ci.sh +++ b/.gitlab/ci.sh @@ -22,7 +22,7 @@ mkdir -p "$GHCUP_BINDIR" export PATH="$GHCUP_BINDIR:$PATH" export BOOTSTRAP_HASKELL_NONINTERACTIVE=1 -export BOOTSTRAP_HASKELL_GHC_VERSION="$GHC_VERSION" +export BOOTSTRAP_HASKELL_GHC_VERSION="${GHC_VERSION:-recommended}" export BOOTSTRAP_HASKELL_CABAL_VERSION="$CABAL_INSTALL_VERSION" export BOOTSTRAP_HASKELL_VERBOSE=1 export BOOTSTRAP_HASKELL_ADJUST_CABAL_CONFIG=yes @@ -45,29 +45,39 @@ case "$(uname -s)" in ;; esac -# Shorten binary names -sed -i.bak -e 's/haskell-language-server/hls/g' \ - -e 's/haskell_language_server/hls/g' \ - haskell-language-server.cabal $CABAL_PROJECT -sed -i.bak -e 's/Paths_haskell_language_server/Paths_hls/g' \ - src/**/*.hs exe/*.hs +case "$(uname)" in + MSYS_*|MINGW*) + # Shorten binary names + sed -i.bak -e 's/haskell-language-server/hls/g' \ + -e 's/haskell_language_server/hls/g' \ + haskell-language-server.cabal $CABAL_PROJECT + sed -i.bak -e 's/Paths_haskell_language_server/Paths_hls/g' \ + src/**/*.hs exe/*.hs -args=( - -O2 - -w "ghc-$GHC_VERSION" - --project-file "$CABAL_PROJECT" - --disable-profiling - --disable-tests - --enable-executable-stripping - ${ADD_CABAL_ARGS} -) + args=( + -O2 + -w "ghc-$GHC_VERSION" + --project-file "$CABAL_PROJECT" + --disable-profiling + --disable-tests + --enable-executable-stripping + ${ADD_CABAL_ARGS} + ) -run cabal v2-build ${args[@]} exe:hls exe:hls-wrapper + run cabal v2-build ${args[@]} exe:hls exe:hls-wrapper -mkdir "$CI_PROJECT_DIR/out" + mkdir "$CI_PROJECT_DIR/out" -cp "$(cabal list-bin ${args[@]} exe:hls)" "$CI_PROJECT_DIR/out/haskell-language-server-${GHC_VERSION}" -cp "$(cabal list-bin ${args[@]} exe:hls-wrapper)" "$CI_PROJECT_DIR/out/haskell-language-server-wrapper" + cp "$(cabal list-bin ${args[@]} exe:hls)" "$CI_PROJECT_DIR/out/haskell-language-server-${GHC_VERSION}" + cp "$(cabal list-bin ${args[@]} exe:hls-wrapper)" "$CI_PROJECT_DIR/out/haskell-language-server-wrapper" + ;; + *) + emake --version + emake GHCUP=ghcup hls + emake GHCUP=ghcup bindist + rm -rf out/*.*.* + ;; +esac cp dist-newstyle/cache/plan.json "$CI_PROJECT_DIR/out/plan.json" diff --git a/.gitlab/common.sh b/.gitlab/common.sh index b6bce698c9..957b4fdc3e 100644 --- a/.gitlab/common.sh +++ b/.gitlab/common.sh @@ -47,3 +47,22 @@ function run() { info "Running $*..." "$@" || ( error "$* failed"; return 1; ) } + +emake() { + if command -v gmake >/dev/null 2>&1 ; then + gmake "$@" + else + make "$@" + fi +} + +mktempdir() { + case "$(uname -s)" in + "Darwin"|"darwin") + mktemp -d -t hls_ci.XXXXXXX + ;; + *) + mktemp -d + ;; + esac +} diff --git a/.gitlab/tar.sh b/.gitlab/tar.sh index ba713d63fa..bca0a5d8a7 100755 --- a/.gitlab/tar.sh +++ b/.gitlab/tar.sh @@ -5,19 +5,21 @@ set -Eeuxo pipefail source "$CI_PROJECT_DIR/.gitlab/common.sh" ls -la out/ -cd out/ # create tarball/zip -HLS_VERSION="$("$CI_PROJECT_DIR/out/haskell-language-server-wrapper" --numeric-version)" TARBALL_PREFIX="haskell-language-server" case "${TARBALL_EXT}" in zip) - zip "${TARBALL_PREFIX}-${TARBALL_ARCHIVE_SUFFIX}-${HLS_VERSION}.zip" haskell-language-server-* + HLS_VERSION="$("$CI_PROJECT_DIR/out/haskell-language-server-wrapper" --numeric-version)" + cd out/ + zip "${TARBALL_PREFIX}-${HLS_VERSION}-${TARBALL_ARCHIVE_SUFFIX}.zip" haskell-language-server-* find . -type f ! -name '*.zip' -delete ;; tar.xz) - tar caf "${TARBALL_PREFIX}-${TARBALL_ARCHIVE_SUFFIX}-${HLS_VERSION}.tar.xz" haskell-language-server-* - find . -type f ! -name '*.tar.xz' -delete + emake --version + HLS_VERSION="$(emake -s -C out/bindist/haskell-language-server-* version)" + emake TARBALL="${TARBALL_PREFIX}-${HLS_VERSION}-${TARBALL_ARCHIVE_SUFFIX}.tar.xz" bindist-tar + rm -rf out/bindist ;; *) fail "Unknown TARBALL_EXT: ${TARBALL_EXT}" diff --git a/.gitlab/test.sh b/.gitlab/test.sh new file mode 100644 index 0000000000..5f08d97898 --- /dev/null +++ b/.gitlab/test.sh @@ -0,0 +1,82 @@ +#!/usr/bin/env bash + +set -Eeuxo pipefail + +source "$CI_PROJECT_DIR/.gitlab/common.sh" + +export GHCUP_INSTALL_BASE_PREFIX="$CI_PROJECT_DIR/toolchain" +export CABAL_DIR="$CI_PROJECT_DIR/cabal" + +case "$(uname)" in + MSYS_*|MINGW*) + export CABAL_DIR="$(cygpath -w "$CABAL_DIR")" + GHCUP_BINDIR="${GHCUP_INSTALL_BASE_PREFIX}/ghcup/bin" + ;; + *) + GHCUP_BINDIR="${GHCUP_INSTALL_BASE_PREFIX}/.ghcup/bin" + ;; +esac + +mkdir -p "$CABAL_DIR" +mkdir -p "$GHCUP_BINDIR" +export PATH="$GHCUP_BINDIR:$PATH" + +export BOOTSTRAP_HASKELL_NONINTERACTIVE=1 +export BOOTSTRAP_HASKELL_GHC_VERSION="${GHC_VERSION:-recommended}" +export BOOTSTRAP_HASKELL_CABAL_VERSION="$CABAL_INSTALL_VERSION" +export BOOTSTRAP_HASKELL_VERBOSE=1 +export BOOTSTRAP_HASKELL_ADJUST_CABAL_CONFIG=yes + +# for some reason the subshell doesn't pick up the arm64 environment on darwin +# and starts installing x86_64 GHC +case "$(uname -s)" in + "Darwin"|"darwin") + case "$(/usr/bin/arch)" in + aarch64|arm64|armv8l) + curl --proto '=https' --tlsv1.2 -sSf https://get-ghcup.haskell.org | arch -arm64 /bin/bash + ;; + *) + curl --proto '=https' --tlsv1.2 -sSf https://get-ghcup.haskell.org | sh + ;; + esac + ;; + *) + curl --proto '=https' --tlsv1.2 -sSf https://get-ghcup.haskell.org | sh + ;; +esac + +# make sure out/ dir is gone, so build host rpaths don't +# kick in (TODO: we should probably remove those) +mv "$CI_PROJECT_DIR/out"/*.tar.xz . +rm -rf "$CI_PROJECT_DIR/out/" + +# cleanup from previous dirty runs +rm -rf "$HOME"/.local/lib/haskell-language-server-* || true + +# install +tar xf *.tar.xz +rm *.tar.xz +cd haskell-language-server-* +INSTALL_DIR=$(dirname "${GHCUP_BINDIR}") || exit 1 +[ -d "$INSTALL_DIR" ] || exit 1 +emake PREFIX="${INSTALL_DIR}" install + +# print rpaths and libdirs +case "$(uname -s)" in + "Darwin"|"darwin") + otool -l "$INSTALL_DIR"/lib/haskell-language-server-*/bin/haskell-language-server-* + ;; + *) + objdump -x "$INSTALL_DIR"/lib/haskell-language-server-*/bin/haskell-language-server-* + ;; +esac +tree "$INSTALL_DIR"/lib/haskell-language-server-* +tree "$INSTALL_DIR"/bin + +tmp_dir=$(mktempdir) +cd "$tmp_dir" +cabal unpack bytestring-0.11.1.0 +cd bytestring-0.11.1.0 +echo "cradle:" > hie.yaml +echo " cabal:" >> hie.yaml +haskell-language-server-wrapper typecheck Data/ByteString.hs diff --git a/GNUmakefile b/GNUmakefile new file mode 100644 index 0000000000..15a0066857 --- /dev/null +++ b/GNUmakefile @@ -0,0 +1,127 @@ +#################################################### +# This makefile's main purpose is to build +# dynamically linked HLS executables on gitlab CI +# and produce appropriate bindists. This can also +# be executed locally on dev machines. +# +# It is not meant to be run by users. +# ################################################## + +UNAME := $(shell uname) +ROOT_DIR:=$(shell dirname $(realpath $(firstword $(MAKEFILE_LIST)))) + +GHC_VERSION ?= + +HLS_VERSION := $(shell grep '^version:' haskell-language-server.cabal | awk '{ print $$2 }') +TARBALL ?= haskell-language-server-$(HLS_VERSION).tar.xz + +CHMOD := chmod +CHMOD_X := $(CHMOD) 755 +INSTALL := install +INSTALL_D := $(INSTALL) -d +INSTALL_X := $(INSTALL) -vm 755 +PATCHELF := patchelf +FIND := find +SED := sed +MKDIR := mkdir +MKDIR_P := $(MKDIR) -p +TAR := tar +TAR_MK := $(TAR) caf +CABAL := cabal +AWK := awk +STRIP := strip +ifeq ($(UNAME), Darwin) +STRIP_S := strip +else +STRIP_S := strip -s +endif +RM := rm +RM_RF := $(RM) -rf +CD := cd + +# by default don't run ghcup +GHCUP ?= echo + +ifeq ($(UNAME), Darwin) +DLL := *.dylib +else +DLL := *.so +endif + +INSTALL_NAME_TOOL := install_name_tool + +STORE_DIR := store +BINDIST_BASE_DIR := out/bindist +BINDIST_OUT_DIR := $(BINDIST_BASE_DIR)/haskell-language-server-$(HLS_VERSION) + +CABAL_ARGS ?= --store-dir=$(ROOT_DIR)/$(STORE_DIR) +CABAL_INSTALL_ARGS ?= --disable-tests --disable-profiling -O2 --overwrite-policy=always --install-method=copy +CABAL_INSTALL := $(CABAL) $(CABAL_ARGS) v2-install + +# set rpath relative to the current executable +# TODO: on darwin, this doesn't overwrite rpath, but just adds to it, +# so we'll have the old rpaths from the build host in there as well +define set_rpath + $(if $(filter Darwin,$(UNAME)), $(INSTALL_NAME_TOOL) -add_rpath "@executable_path/$(1)" "$(2)", $(PATCHELF) --force-rpath --set-rpath "\$$ORIGIN/$(1)" "$(2)") +endef + +hls: bindist/ghcs + for ghc in $(shell [ -e "bindist/ghcs-`uname`" ] && cat "bindist/ghcs-`uname`" || cat "bindist/ghcs") ; do \ + $(GHCUP) -v install ghc `echo $$ghc | $(AWK) -F ',' '{ print $$1 }'` && \ + $(GHCUP) -v gc -p -s -c && \ + $(MAKE) GHC_VERSION=`echo $$ghc | $(AWK) -F ',' '{ print $$1 }'` PROJECT_FILE=`echo $$ghc | $(AWK) -F ',' '{ print $$2 }'` hls-ghc && \ + $(GHCUP) -v rm ghc `echo $$ghc | $(AWK) -F ',' '{ print $$1 }'` ; \ + done + +hls-ghc: + $(MKDIR_P) out/ + @if test -z "$(GHC_VERSION)" ; then echo >&2 "GHC_VERSION is not set" ; false ; fi + @if test -z "$(PROJECT_FILE)" ; then echo >&2 "PROJECT_FILE is not set" ; false ; fi + $(CABAL_INSTALL) --project-file="$(PROJECT_FILE)" -w "ghc-$(GHC_VERSION)" $(CABAL_INSTALL_ARGS) --installdir="$(ROOT_DIR)/out/$(GHC_VERSION)" exe:haskell-language-server exe:haskell-language-server-wrapper + $(STRIP_S) "$(ROOT_DIR)/out/$(GHC_VERSION)/haskell-language-server" + $(STRIP_S) "$(ROOT_DIR)/out/$(GHC_VERSION)/haskell-language-server-wrapper" + +bindist: + for ghc in $(shell [ -e "bindist/ghcs-`uname`" ] && cat "bindist/ghcs-`uname`" || cat "bindist/ghcs") ; do \ + $(GHCUP) -v install ghc `echo $$ghc | $(AWK) -F ',' '{ print $$1 }'` && \ + $(GHCUP) -v gc -p -s -c && \ + $(MAKE) GHC_VERSION=`echo $$ghc | $(AWK) -F ',' '{ print $$1 }'` bindist-ghc && \ + $(GHCUP) -v rm ghc `echo $$ghc | $(AWK) -F ',' '{ print $$1 }'` ; \ + done + $(SED) -e "s/@@HLS_VERSION@@/$(HLS_VERSION)/" \ + bindist/GNUmakefile.in > "$(BINDIST_OUT_DIR)/GNUmakefile" + $(INSTALL_D) "$(BINDIST_OUT_DIR)/scripts/" + $(INSTALL_X) "bindist/relpath.sh" "$(BINDIST_OUT_DIR)/scripts/relpath.sh" + +bindist-tar: + $(CD) "$(BINDIST_BASE_DIR)" ; $(TAR_MK) "$(ROOT_DIR)/out/$(TARBALL)" "haskell-language-server-$(HLS_VERSION)" + +bindist-ghc: + if test -z "$(GHC_VERSION)" ; then echo >&2 "GHC_VERSION is not set" ; false ; fi + $(MKDIR_P) "$(BINDIST_OUT_DIR)/bin" + $(MKDIR_P) "$(BINDIST_OUT_DIR)/lib/$(GHC_VERSION)" + $(INSTALL_D) "$(BINDIST_OUT_DIR)/bin/" + $(INSTALL_X) "out/$(GHC_VERSION)/haskell-language-server" "$(BINDIST_OUT_DIR)/bin/haskell-language-server-$(GHC_VERSION)" + $(call set_rpath,../lib/$(GHC_VERSION),$(BINDIST_OUT_DIR)/bin/haskell-language-server-$(GHC_VERSION)) + $(SED) \ + -e "s/@@EXE_NAME@@/haskell-language-server-$(GHC_VERSION)/" \ + -e "s/@@GHC_VERSION@@/$(GHC_VERSION)/" \ + -e "s/@@ABI_HASHES@@/$(shell for dep in `ghc-pkg-$(GHC_VERSION) --global list --simple-output` ; do printf "%s:" "$$dep" && ghc-pkg-$(GHC_VERSION) field $$dep abi --simple-output ; done | tr '\n' ' ' | xargs)/" \ + bindist/wrapper.in > "$(BINDIST_OUT_DIR)/haskell-language-server-$(GHC_VERSION).in" + $(CHMOD_X) "$(BINDIST_OUT_DIR)/haskell-language-server-$(GHC_VERSION).in" + $(INSTALL_D) "$(BINDIST_OUT_DIR)/bin/" + $(INSTALL_X) "out/$(GHC_VERSION)/haskell-language-server-wrapper" "$(BINDIST_OUT_DIR)/bin/haskell-language-server-wrapper" + $(INSTALL_D) "$(ROOT_DIR)/$(BINDIST_OUT_DIR)/lib/$(GHC_VERSION)" + $(FIND) "$(STORE_DIR)/ghc-$(GHC_VERSION)" -type f -name "$(DLL)" -execdir $(INSTALL_X) "{}" "$(ROOT_DIR)/$(BINDIST_OUT_DIR)/lib/$(GHC_VERSION)/{}" \; + $(FIND) "$(ROOT_DIR)/$(BINDIST_OUT_DIR)/lib/$(GHC_VERSION)" -type f -name '$(DLL)' -execdir $(call set_rpath,,{}) \; + +version: + @echo "$(HLS_VERSION)" + +clean: + $(RM_RF) out/* + +clean-all: + $(RM_RF) out/* $(STORE_DIR) + +.PHONY: hls hls-ghc bindist bindist-ghc bindist-tar clean clean-all install-ghcs diff --git a/bindist/GNUmakefile.in b/bindist/GNUmakefile.in new file mode 100644 index 0000000000..5e1b01de26 --- /dev/null +++ b/bindist/GNUmakefile.in @@ -0,0 +1,40 @@ +DESTDIR ?= +PREFIX ?= /usr/local +LIBDIR ?= $(PREFIX)/lib +BINDIR ?= $(PREFIX)/bin + +HLS_VERSION := @@HLS_VERSION@@ + +INSTALL := install +INSTALL_D := $(INSTALL) -d +INSTALL_X := $(INSTALL) -vm 755 +SED := sed +CHMOD := chmod +CHMOD_X := $(CHMOD) 755 +LN := ln +LN_S := $(LN) -sf + +install: + $(INSTALL_D) "$(DESTDIR)$(BINDIR)" + $(INSTALL_D) "$(DESTDIR)$(LIBDIR)/haskell-language-server-$(HLS_VERSION)/lib" + $(INSTALL_D) "$(DESTDIR)$(LIBDIR)/haskell-language-server-$(HLS_VERSION)/bin" + for f in $(wildcard lib/*/*) ; do \ + $(INSTALL_D) "$(DESTDIR)$(LIBDIR)/haskell-language-server-$(HLS_VERSION)/`dirname $$f`" && \ + $(INSTALL_X) "$$f" "$(DESTDIR)$(LIBDIR)/haskell-language-server-$(HLS_VERSION)/$$f" ; \ + done + for b in $(wildcard bin/*) ; do \ + $(INSTALL_D) "$(DESTDIR)$(LIBDIR)/haskell-language-server-$(HLS_VERSION)/" && \ + $(INSTALL_X) "$$b" "$(DESTDIR)$(LIBDIR)/haskell-language-server-$(HLS_VERSION)/$$b" ; \ + done + for h in $(wildcard haskell-language-server-*.in) ; do \ + $(SED) -e "s#@@EXE_DIR@@#$(LIBDIR)/haskell-language-server-$(HLS_VERSION)/bin/#" \ + "$$h" > "$(DESTDIR)$(BINDIR)/$${h%.in}" && \ + $(CHMOD_X) "$(DESTDIR)$(BINDIR)/$${h%.in}" ; \ + done + $(LN_S) "`scripts/relpath.sh "$(BINDIR)" "$(LIBDIR)/haskell-language-server-$(HLS_VERSION)/bin/haskell-language-server-wrapper"`" \ + "$(DESTDIR)$(BINDIR)/haskell-language-server-wrapper" + +version: + @echo "$(HLS_VERSION)" + +.PHONY: install diff --git a/bindist/ghcs b/bindist/ghcs new file mode 100644 index 0000000000..f61eef13e6 --- /dev/null +++ b/bindist/ghcs @@ -0,0 +1,3 @@ +8.10.7,cabal.project +9.0.2,cabal-ghc90.project +9.2.1,cabal-ghc921.project diff --git a/bindist/ghcs-FreeBSD b/bindist/ghcs-FreeBSD new file mode 100644 index 0000000000..cab2c69bb2 --- /dev/null +++ b/bindist/ghcs-FreeBSD @@ -0,0 +1,2 @@ +8.10.7,cabal.project +9.0.2,cabal-ghc90.project diff --git a/bindist/relpath.sh b/bindist/relpath.sh new file mode 100755 index 0000000000..3385155e97 --- /dev/null +++ b/bindist/relpath.sh @@ -0,0 +1,105 @@ +#!/bin/sh + +# POSIX shell implementation of `realpath --relative-to=$1 $2. +# This is an adaptation of the implementation from +# . + +# returns relative path to $2=$target from $1=$source +## NOTE : path are compared in text only. They don’t have to exist +## and they WONT be normalized/escaped +## Result in "$return_value"# both $1 and $2 are absolute paths beginning with / + +# @FUNCTION: die +# @USAGE: [msg] +# @DESCRIPTION: +# Exits the shell script with status code 2 +# and prints the given message in red to STDERR, if any. +die() { + (>&2 echo "$1") + exit 2 +} + +# @FUNCTION: posix_realpath +# @USAGE: +# @DESCRIPTION: +# Portably gets the realpath and prints it to stdout. +# This was initially inspired by +# https://gist.github.com/tvlooy/cbfbdb111a4ebad8b93e +# and +# https://stackoverflow.com/a/246128 +# +# If the file does not exist, just prints it appended to the current directory. +# @STDOUT: realpath of the given file +posix_realpath() { + [ -z "$1" ] && die "Internal error: no argument given to posix_realpath" + current_loop=0 + max_loops=50 + mysource=$1 + + while [ -h "${mysource}" ]; do + current_loop=$((current_loop+1)) + mydir="$( cd -P "$( dirname "${mysource}" )" > /dev/null 2>&1 && pwd )" + mysource="$(readlink "${mysource}")" + [ "${mysource%${mysource#?}}"x != '/x' ] && mysource="${mydir}/${mysource}" + + if [ ${current_loop} -gt ${max_loops} ] ; then + (>&2 echo "${1}: Too many levels of symbolic links") + exit 1 + fi + done + mydir="$( cd -P "$( dirname "${mysource}" )" > /dev/null 2>&1 && pwd )" + + # TODO: better distinguish between "does not exist" and "permission denied" + if [ -z "${mydir}" ] ; then + (>&2 echo "${1}: Permission denied") + echo "$(pwd)/$1" + else + echo "${mydir%/}/$(basename "${mysource}")" + fi + + unset current_loop max_loops mysource mydir +} + + +src="$(posix_realpath $1)" || exit 1 +target="$(posix_realpath $2)" || exit 1 + +common_part="$src" +result="" + +while test "${target#$common_part}" = "${target}" ; do + #echo "common_part is now : \"$common_part\"" + #echo "result is now : \"$result\"" + #echo "target#common_part : \"${target#$common_part}\"" + # no match, means that candidate common part is not correct + # go up one level (reduce common part) + common_part="$(dirname "$common_part")" + # and record that we went back + if test -z "$result" ; then + result=".." + else + result="../$result" + fi +done + +#echo "common_part is : \"$common_part\"" + +if test "$common_part" = "/" ; then + # special case for root (no common path) + result="$result/" +fi + +# since we now have identified the common part, +# compute the non-common part +forward_part="${target#$common_part}" +#echo "forward_part = \"$forward_part\"" + +if test -n "$result" && test -n "$forward_part" ; then + #echo "(simple concat)" + result="$result$forward_part" +elif test -n "$forward_part" ; then + #echo "(concat with slash removal)" + result="$(printf "%s" "$forward_part" | cut -c 1-)" +fi + +printf "%s" "$result" diff --git a/bindist/wrapper.in b/bindist/wrapper.in new file mode 100644 index 0000000000..e29edac128 --- /dev/null +++ b/bindist/wrapper.in @@ -0,0 +1,179 @@ +#!/bin/sh + +exedir="@@EXE_DIR@@" +executablename="@@EXE_NAME@@" +GHC_VERSION="@@GHC_VERSION@@" +ABI_HASHES="@@ABI_HASHES@@" + +debug_msg() { + if [ -n "$HLS_WRAPPER_DEBUG" ] ; then + (>&2 printf "\\033[0;34m%s\\033[0m\\n" "$1") + fi +} + +err_msg() { + if [ -n "$HLS_WRAPPER_DEBUG" ] ; then + (>&2 printf "\\033[0;31m%s\\033[0m\\n" "$1") + elif [ -n "$HLS_WRAPPER_VERBOSE" ] ; then + (>&2 printf "\\033[0;31m%s\\033[0m\\n" "$1") + fi +} + +instruction_msg() { + (>&2 printf "\\033[0;35m%s\\033[0m\\n" "$1") +} + +err_fix() { + instruction_msg "" + instruction_msg "Consider installing ghc-${GHC_VERSION} via ghcup" + instruction_msg "or build HLS from source." +} + +err_ghc_pkg() { + err_msg "Could not find a ghc-pkg binary (found: $1)!" +} + +err_abi() { + err_msg "GHC ABIs don't match!" + err_msg "" + err_msg "Expected: ${ABI_HASHES}" + err_msg "Got: $1" +} + +err_ver() { + err_msg "GHC versions don't match!" + err_msg "" + err_msg "Expected: ${GHC_VERSION}" + err_msg "Got: $1" +} + +# Check the version of GHC and the ABI. +check_ghc() { + { [ -z "$1" ] || [ -z "$2" ] || [ -z "$3" ] ;} && debug_msg "internal error: not enough arguments to check_ghc: 1:$1,2:$2,3:$3" && return 4 + + check_ghc_libdir=$1 + check_ghc_bin=$2 + GHC_PKG=$3 + check_ghc_ver="$("${check_ghc_bin}" --numeric-version 2>/dev/null)" + + # check version + if [ "${check_ghc_ver}" = "${GHC_VERSION}" ] ; then + # check ABI + if "${GHC_PKG}" --version >/dev/null ; then + : + elif "${GHC_PKG}-${GHC_VERSION}" --version >/dev/null ; then + GHC_PKG=${GHC_PKG}-${GHC_VERSION} + else + err_ghc_pkg "${GHC_PKG}" + unset GHC_LIBDIR + return 1 + fi + PKGCONF="${check_ghc_libdir}/package.conf.d" + MY_ABI_HASHES="$(for dep in $("${GHC_PKG}" --global --global-package-db "$PKGCONF" list --simple-output) ; do printf "%s:" "${dep}" && "${GHC_PKG}" --global --global-package-db "$PKGCONF" field "${dep}" abi --simple-output ; done | tr '\n' ' ' | xargs)" + if [ "${ABI_HASHES}" != "${MY_ABI_HASHES}" ] ; then + err_abi "${MY_ABI_HASHES}" + return 3 + fi + unset PKGCONF + else + err_ver "${check_ghc_ver}" + unset GHC_LIBDIR + return 2 + fi + + unset check_ghc_libdir check_ghc_bindir GHC_PKG check_ghc_ver +} + +# Infer ghc-pkg from the given ghc path. Doesn't check for existence of any +# components. +infer_ghc_pkg() { + infer_ghc_path=$1 + infer_ghc_bin=${infer_ghc_path##**/} + infer_ghc_ver_suffix=${infer_ghc_bin#ghc} + path_prefix="$(dirname "${infer_ghc_path}")" + + if [ "${path_prefix}" = "." ] ; then + echo "ghc-pkg${infer_ghc_ver_suffix}" + elif [ "${path_prefix}" = "/" ] ; then + echo "${path_prefix}ghc-pkg${infer_ghc_ver_suffix}" + else + echo "${path_prefix}/ghc-pkg${infer_ghc_ver_suffix}" + fi + unset infer_ghc_path infer_ghc_bin infer_ghc_ver_suffix path_prefix +} + +# try GHC_LIBDIR from the environment (e.g. user set it, or haskell-language-server-wrapper) +if [ -n "${GHC_LIBDIR}" ] && + [ -n "${GHC_BIN}" ] && + { debug_msg "Trying method: GHC_LIBDIR and GHC_BIN from env" ; HLS_WRAPPER_VERBOSE=1 ; check_ghc "${GHC_LIBDIR}" "${GHC_BIN}" "$(infer_ghc_pkg "${GHC_BIN}")" || { err_fix ; exit 1 ; } ; } +then + : +# try GHC_BIN from the environment (e.g. user set it) +elif [ -n "${GHC_BIN}" ] && + GHC_LIBDIR="$("${GHC_BIN}" --print-libdir)" && + { debug_msg "Trying method: GHC_BIN from env" ; HLS_WRAPPER_VERBOSE=1 ; check_ghc "${GHC_LIBDIR}" "${GHC_BIN}" "$(infer_ghc_pkg "${GHC_BIN}")" || { err_fix ; exit 2 ; } ; } +then + : +# try ghcup +elif command -v ghcup >/dev/null && + GHC_BIN="$(ghcup whereis ghc "${GHC_VERSION}")" && + GHC_LIBDIR="$("${GHC_BIN}" --print-libdir)" && + { debug_msg "Trying method: ghcup" ; check_ghc "${GHC_LIBDIR}" "${GHC_BIN}" "$(infer_ghc_pkg "${GHC_BIN}")" ; } +then + : +# try ghc-${GHC_VERSION} +elif command -v ghc-${GHC_VERSION} >/dev/null && + GHC_LIBDIR="$("ghc-${GHC_VERSION}" --print-libdir)" && + { debug_msg "Trying method: ghc-${GHC_VERSION} in PATH" ; check_ghc "${GHC_LIBDIR}" "ghc-${GHC_VERSION}" "$(infer_ghc_pkg "ghc-${GHC_VERSION}")" ; } +then + : +# try ghc +elif command -v ghc >/dev/null && + GHC_LIBDIR="$(ghc --print-libdir)" && + { debug_msg "Trying method: ghc in PATH" ; check_ghc "${GHC_LIBDIR}" "ghc" "$(infer_ghc_pkg "ghc")" ; } +then + : +# try stack +elif command -v stack >/dev/null && + GHC_BIN="$(cd "$(mktemp -d)" && stack --no-system-ghc --no-install-ghc --resolver "ghc-${GHC_VERSION}" exec sh -- -c 'command -v ghc')" && + GHC_LIBDIR="$("${GHC_BIN}" --print-libdir)" && + { debug_msg "Trying method: stack" ; check_ghc "${GHC_LIBDIR}" "${GHC_BIN}" "$(infer_ghc_pkg "${GHC_BIN}")" ; } +then + : +else + HLS_WRAPPER_VERBOSE=1 + err_msg "All methods exhausted!" + err_msg "Couldn't find a working/matching GHC installation" + err_fix + err_msg "exiting..." + exit 42 +fi + +debug_msg "Found GHC libdir at: ${GHC_LIBDIR}" + +case "$(uname -s)" in + "Darwin"|"darwin") + if [ -n "$DYLD_LIBRARY_PATH" ] ; then + DYLD_LIBRARY_PATH="$(for i in "${GHC_LIBDIR}"/* ; do [ -d "$i" ] && printf "%s" "$i:" ; done)$DYLD_LIBRARY_PATH" + debug_msg "Exporting DYLD_LIBRARY_PATH=${DYLD_LIBRARY_PATH}" + export DYLD_LIBRARY_PATH + else + DYLD_LIBRARY_PATH="$(for i in "${GHC_LIBDIR}"/* ; do [ -d "$i" ] && printf "%s" "$i:" ; done | sed 's/:$//')" + debug_msg "Exporting DYLD_LIBRARY_PATH=${DYLD_LIBRARY_PATH}" + export DYLD_LIBRARY_PATH + fi + ;; + *) + if [ -n "$LD_LIBRARY_PATH" ] ; then + LD_LIBRARY_PATH="$(for i in "${GHC_LIBDIR}"/* ; do [ -d "$i" ] && printf "%s" "$i:" ; done)$LD_LIBRARY_PATH" + debug_msg "Exporting LD_LIBRARY_PATH=${LD_LIBRARY_PATH}" + export LD_LIBRARY_PATH + else + LD_LIBRARY_PATH="$(for i in "${GHC_LIBDIR}"/* ; do [ -d "$i" ] && printf "%s" "$i:" ; done | sed 's/:$//')" + debug_msg "Exporting LD_LIBRARY_PATH=${LD_LIBRARY_PATH}" + export LD_LIBRARY_PATH + fi + ;; +esac + +exec "${exedir}/${executablename}" ${1+"$@"} diff --git a/docs/contributing/releases.md b/docs/contributing/releases.md index ca1c78a143..ff5dfb6353 100644 --- a/docs/contributing/releases.md +++ b/docs/contributing/releases.md @@ -18,6 +18,7 @@ and it is being used in nix environments. ### prerelease sanity checks +- [ ] set the supported GHC versions and their corresponding cabal project-files in `bindist/ghcs` according to the [GHC version deprecation policy](../supported-versions.md#ghc-version-deprecation-policy) - [ ] [trigger manually](https://docs.github.com/es/actions/managing-workflow-runs/manually-running-a-workflow) the hackage workflow *without* uploading the packages - [ ] trigger manually the build workflow - [ ] create a prerelease tag `${version}-check-gitlab` and push it to the [project repo in gitlab](https://gitlab.haskell.org/haskell/haskell-language-server) to check the build is fine diff --git a/exe/Wrapper.hs b/exe/Wrapper.hs index 2cb2084b0c..f98760e89e 100644 --- a/exe/Wrapper.hs +++ b/exe/Wrapper.hs @@ -1,9 +1,12 @@ {-# LANGUAGE RecordWildCards #-} +{-# LANGUAGE NamedFieldPuns #-} +{-# LANGUAGE CPP #-} -- | This module is based on the hie-wrapper.sh script in -- https://github.com/alanz/vscode-hie-server module Main where import Control.Monad.Extra +import Data.Char (isSpace) import Data.Default import Data.Foldable import Data.List @@ -19,7 +22,12 @@ import System.Exit import System.FilePath import System.IO import System.Info +#ifndef mingw32_HOST_OS +import System.Posix.Process (executeFile) +import qualified Data.Map.Strict as Map +#else import System.Process +#endif -- --------------------------------------------------------------------- @@ -45,7 +53,10 @@ main = do BiosMode PrintCradleType -> print =<< findProjectCradle - + PrintLibDir -> do + cradle <- findProjectCradle' False + (CradleSuccess libdir) <- HieBios.getRuntimeGhcLibDir cradle + putStr libdir _ -> launchHaskellLanguageServer args launchHaskellLanguageServer :: Arguments -> IO () @@ -96,10 +107,19 @@ launchHaskellLanguageServer parsedArgs = do mexes <- traverse findExecutable candidates case asum mexes of - Nothing -> hPutStrLn stderr $ "Cannot find any haskell-language-server exe, looked for: " ++ intercalate ", " candidates + Nothing -> die $ "Cannot find any haskell-language-server exe, looked for: " ++ intercalate ", " candidates Just e -> do hPutStrLn stderr $ "Launching haskell-language-server exe at:" ++ e +#ifdef mingw32_HOST_OS callProcess e args +#else + let Cradle { cradleOptsProg = CradleAction { runGhcCmd } } = cradle + (CradleSuccess ghcBinary) <- fmap trim <$> runGhcCmd ["-v0", "-package-env=-", "-e", "putStr =<< System.Environment.getExecutablePath"] + (CradleSuccess libdir) <- HieBios.getRuntimeGhcLibDir cradle + env <- Map.fromList <$> getEnvironment + let newEnv = Map.insert "GHC_BIN" ghcBinary $ Map.insert "GHC_LIBDIR" libdir env + executeFile e True args (Just (Map.toList newEnv)) +#endif -- | Version of 'getRuntimeGhcVersion' that dies if we can't get it, and also -- checks to see if the tool is missing if it is one of @@ -130,15 +150,24 @@ getRuntimeGhcVersion' cradle = do ++ show cradle findProjectCradle :: IO (Cradle Void) -findProjectCradle = do +findProjectCradle = findProjectCradle' True + +findProjectCradle' :: Bool -> IO (Cradle Void) +findProjectCradle' log = do d <- getCurrentDirectory let initialFp = d "a" hieYaml <- Session.findCradle def initialFp -- Some log messages - case hieYaml of - Just yaml -> hPutStrLn stderr $ "Found \"" ++ yaml ++ "\" for \"" ++ initialFp ++ "\"" - Nothing -> hPutStrLn stderr "No 'hie.yaml' found. Try to discover the project type!" + when log $ + case hieYaml of + Just yaml -> hPutStrLn stderr $ "Found \"" ++ yaml ++ "\" for \"" ++ initialFp ++ "\"" + Nothing -> hPutStrLn stderr "No 'hie.yaml' found. Try to discover the project type!" Session.loadCradle def hieYaml d + +trim :: String -> String +trim s = case lines s of + [] -> s + ls -> dropWhileEnd isSpace $ last ls diff --git a/haskell-language-server.cabal b/haskell-language-server.cabal index 89d38e953d..92b5305f45 100644 --- a/haskell-language-server.cabal +++ b/haskell-language-server.cabal @@ -442,6 +442,10 @@ executable haskell-language-server-wrapper , optparse-applicative , optparse-simple , process + if !os(windows) + build-depends: + unix + , containers default-language: Haskell2010 diff --git a/src/Ide/Arguments.hs b/src/Ide/Arguments.hs index 0f1f5eb8a5..8c3fa98981 100644 --- a/src/Ide/Arguments.hs +++ b/src/Ide/Arguments.hs @@ -1,151 +1,153 @@ --- Copyright (c) 2019 The DAML Authors. All rights reserved. --- SPDX-License-Identifier: Apache-2.0 -{-# LANGUAGE CPP #-} -{-# LANGUAGE RecordWildCards #-} -{-# LANGUAGE TemplateHaskell #-} -{-# LANGUAGE TupleSections #-} -{-# OPTIONS_GHC -Wno-dodgy-imports #-} -- GHC no longer exports def in GHC 8.6 and above - -module Ide.Arguments - ( Arguments(..) - , GhcideArguments(..) - , PrintVersion(..) - , BiosAction(..) - , getArguments - , haskellLanguageServerVersion - , haskellLanguageServerNumericVersion - ) where - -import Data.Version -import Development.IDE (IdeState) -import Development.IDE.Main (Command (..), commandP) -import GitHash (giHash, tGitInfoCwdTry) -import Ide.Types (IdePlugins) -import Options.Applicative -import Paths_haskell_language_server -import System.Environment - --- --------------------------------------------------------------------- - -data Arguments - = VersionMode PrintVersion - | ProbeToolsMode - | ListPluginsMode - | BiosMode BiosAction - | Ghcide GhcideArguments - | VSCodeExtensionSchemaMode - | DefaultConfigurationMode - -data GhcideArguments = GhcideArguments - {argsCommand :: Command - ,argsCwd :: Maybe FilePath - ,argsShakeProfiling :: Maybe FilePath - ,argsTesting :: Bool - ,argsExamplePlugin :: Bool - -- These next two are for compatibility with existing hie clients, allowing - -- them to just change the name of the exe and still work. - , argsDebugOn :: Bool - , argsLogFile :: Maybe String - , argsThreads :: Int - , argsProjectGhcVersion :: Bool - } deriving Show - -data PrintVersion - = PrintVersion - | PrintNumericVersion - deriving (Show, Eq, Ord) - -data BiosAction - = PrintCradleType - deriving (Show, Eq, Ord) - -getArguments :: String -> IdePlugins IdeState -> IO Arguments -getArguments exeName plugins = execParser opts - where - opts = info (( - VersionMode <$> printVersionParser exeName - <|> probeToolsParser exeName - <|> listPluginsParser - <|> BiosMode <$> biosParser - <|> Ghcide <$> arguments plugins - ) - <**> helper) - ( fullDesc - <> progDesc "Used as a test bed to check your IDE Client will work" - <> header (exeName ++ " - GHC Haskell LSP server")) - -printVersionParser :: String -> Parser PrintVersion -printVersionParser exeName = - flag' PrintVersion - (long "version" <> help ("Show " ++ exeName ++ " and GHC versions")) - <|> - flag' PrintNumericVersion - (long "numeric-version" <> help ("Show numeric version of " ++ exeName)) - -biosParser :: Parser BiosAction -biosParser = - flag' PrintCradleType - (long "print-cradle" <> help "Print the project cradle type") - -probeToolsParser :: String -> Parser Arguments -probeToolsParser exeName = - flag' ProbeToolsMode - (long "probe-tools" <> help ("Show " ++ exeName ++ " version and other tools of interest")) - -listPluginsParser :: Parser Arguments -listPluginsParser = - flag' ListPluginsMode - (long "list-plugins" <> help "List all available plugins") - -arguments :: IdePlugins IdeState -> Parser GhcideArguments -arguments plugins = GhcideArguments - <$> (commandP plugins <|> lspCommand <|> checkCommand) - <*> optional (strOption $ long "cwd" <> metavar "DIR" - <> help "Change to this directory") - <*> optional (strOption $ long "shake-profiling" <> metavar "DIR" - <> help "Dump profiling reports to this directory") - <*> switch (long "test" - <> help "Enable additional lsp messages used by the testsuite") - <*> switch (long "example" - <> help "Include the Example Plugin. For Plugin devs only") - - <*> switch - ( long "debug" - <> short 'd' - <> help "Generate debug output" - ) - <*> optional (strOption - ( long "logfile" - <> short 'l' - <> metavar "LOGFILE" - <> help "File to log to, defaults to stdout" - )) - <*> option auto - (short 'j' - <> help "Number of threads (0: automatic)" - <> metavar "NUM" - <> value 0 - <> showDefault - ) - <*> switch (long "project-ghc-version" - <> help "Work out the project GHC version and print it") - where - lspCommand = LSP <$ flag' True (long "lsp" <> help "Start talking to an LSP server") - checkCommand = Check <$> many (argument str (metavar "FILES/DIRS...")) - --- --------------------------------------------------------------------- - -haskellLanguageServerNumericVersion :: String -haskellLanguageServerNumericVersion = showVersion version - -haskellLanguageServerVersion :: IO String -haskellLanguageServerVersion = do - path <- getExecutablePath - let gi = $$tGitInfoCwdTry - gitHashSection = case gi of - Right gi -> " (GIT hash: " <> giHash gi <> ")" - Left _ -> "" - return $ "haskell-language-server version: " <> haskellLanguageServerNumericVersion - <> " (GHC: " <> VERSION_ghc - <> ") (PATH: " <> path <> ")" - <> gitHashSection +-- Copyright (c) 2019 The DAML Authors. All rights reserved. +-- SPDX-License-Identifier: Apache-2.0 +{-# LANGUAGE CPP #-} +{-# LANGUAGE RecordWildCards #-} +{-# LANGUAGE TemplateHaskell #-} +{-# LANGUAGE TupleSections #-} +{-# OPTIONS_GHC -Wno-dodgy-imports #-} -- GHC no longer exports def in GHC 8.6 and above + +module Ide.Arguments + ( Arguments(..) + , GhcideArguments(..) + , PrintVersion(..) + , BiosAction(..) + , getArguments + , haskellLanguageServerVersion + , haskellLanguageServerNumericVersion + ) where + +import Data.Version +import Development.IDE (IdeState) +import Development.IDE.Main (Command (..), commandP) +import GitHash (giHash, tGitInfoCwdTry) +import Ide.Types (IdePlugins) +import Options.Applicative +import Paths_haskell_language_server +import System.Environment + +-- --------------------------------------------------------------------- + +data Arguments + = VersionMode PrintVersion + | ProbeToolsMode + | ListPluginsMode + | BiosMode BiosAction + | Ghcide GhcideArguments + | VSCodeExtensionSchemaMode + | DefaultConfigurationMode + | PrintLibDir + +data GhcideArguments = GhcideArguments + {argsCommand :: Command + ,argsCwd :: Maybe FilePath + ,argsShakeProfiling :: Maybe FilePath + ,argsTesting :: Bool + ,argsExamplePlugin :: Bool + -- These next two are for compatibility with existing hie clients, allowing + -- them to just change the name of the exe and still work. + , argsDebugOn :: Bool + , argsLogFile :: Maybe String + , argsThreads :: Int + , argsProjectGhcVersion :: Bool + } deriving Show + +data PrintVersion + = PrintVersion + | PrintNumericVersion + deriving (Show, Eq, Ord) + +data BiosAction + = PrintCradleType + deriving (Show, Eq, Ord) + +getArguments :: String -> IdePlugins IdeState -> IO Arguments +getArguments exeName plugins = execParser opts + where + opts = info (( + VersionMode <$> printVersionParser exeName + <|> probeToolsParser exeName + <|> listPluginsParser + <|> BiosMode <$> biosParser + <|> Ghcide <$> arguments plugins + <|> flag' PrintLibDir (long "print-libdir" <> help ("Print project GHCs libdir")) + ) + <**> helper) + ( fullDesc + <> progDesc "Used as a test bed to check your IDE Client will work" + <> header (exeName ++ " - GHC Haskell LSP server")) + +printVersionParser :: String -> Parser PrintVersion +printVersionParser exeName = + flag' PrintVersion + (long "version" <> help ("Show " ++ exeName ++ " and GHC versions")) + <|> + flag' PrintNumericVersion + (long "numeric-version" <> help ("Show numeric version of " ++ exeName)) + +biosParser :: Parser BiosAction +biosParser = + flag' PrintCradleType + (long "print-cradle" <> help "Print the project cradle type") + +probeToolsParser :: String -> Parser Arguments +probeToolsParser exeName = + flag' ProbeToolsMode + (long "probe-tools" <> help ("Show " ++ exeName ++ " version and other tools of interest")) + +listPluginsParser :: Parser Arguments +listPluginsParser = + flag' ListPluginsMode + (long "list-plugins" <> help "List all available plugins") + +arguments :: IdePlugins IdeState -> Parser GhcideArguments +arguments plugins = GhcideArguments + <$> (commandP plugins <|> lspCommand <|> checkCommand) + <*> optional (strOption $ long "cwd" <> metavar "DIR" + <> help "Change to this directory") + <*> optional (strOption $ long "shake-profiling" <> metavar "DIR" + <> help "Dump profiling reports to this directory") + <*> switch (long "test" + <> help "Enable additional lsp messages used by the testsuite") + <*> switch (long "example" + <> help "Include the Example Plugin. For Plugin devs only") + + <*> switch + ( long "debug" + <> short 'd' + <> help "Generate debug output" + ) + <*> optional (strOption + ( long "logfile" + <> short 'l' + <> metavar "LOGFILE" + <> help "File to log to, defaults to stdout" + )) + <*> option auto + (short 'j' + <> help "Number of threads (0: automatic)" + <> metavar "NUM" + <> value 0 + <> showDefault + ) + <*> switch (long "project-ghc-version" + <> help "Work out the project GHC version and print it") + where + lspCommand = LSP <$ flag' True (long "lsp" <> help "Start talking to an LSP server") + checkCommand = Check <$> many (argument str (metavar "FILES/DIRS...")) + +-- --------------------------------------------------------------------- + +haskellLanguageServerNumericVersion :: String +haskellLanguageServerNumericVersion = showVersion version + +haskellLanguageServerVersion :: IO String +haskellLanguageServerVersion = do + path <- getExecutablePath + let gi = $$tGitInfoCwdTry + gitHashSection = case gi of + Right gi -> " (GIT hash: " <> giHash gi <> ")" + Left _ -> "" + return $ "haskell-language-server version: " <> haskellLanguageServerNumericVersion + <> " (GHC: " <> VERSION_ghc + <> ") (PATH: " <> path <> ")" + <> gitHashSection diff --git a/src/Ide/Main.hs b/src/Ide/Main.hs index e4b7cec41e..3925c985a7 100644 --- a/src/Ide/Main.hs +++ b/src/Ide/Main.hs @@ -23,6 +23,8 @@ import qualified Development.IDE.Main as Main import qualified Development.IDE.Session as Session import Development.IDE.Types.Logger as G import qualified Development.IDE.Types.Options as Ghcide +import HIE.Bios.Types +import qualified HIE.Bios.Environment as HieBios import Ide.Arguments import Ide.Logger import Ide.Plugin.ConfigUtils (pluginsToDefaultConfig, @@ -31,7 +33,9 @@ import Ide.Types (IdePlugins, PluginId (PluginId), ipMap) import Ide.Version import qualified Language.LSP.Server as LSP +import System.Directory import qualified System.Directory.Extra as IO +import System.FilePath import System.IO import qualified System.Log.Logger as L @@ -76,6 +80,13 @@ defaultMain args idePlugins = do DefaultConfigurationMode -> do LBS.putStrLn $ A.encodePretty $ pluginsToDefaultConfig idePlugins + PrintLibDir -> do + d <- getCurrentDirectory + let initialFp = d "a" + hieYaml <- Session.findCradle def initialFp + cradle <- Session.loadCradle def hieYaml d + (CradleSuccess libdir) <- HieBios.getRuntimeGhcLibDir cradle + putStr libdir -- ---------------------------------------------------------------------