diff --git a/.circleci/config.yml b/.circleci/config.yml index 51bba39..fba3e8c 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -137,7 +137,7 @@ save: &save save_cache: key: v1-{{ .Environment.CIRCLE_JOB }}-{{ .Branch }} paths: - - ~/.local + - ~/.local/bin - ~/.cabal - ~/.stack - ~/.ghcup diff --git a/.cirrus.yml b/.cirrus.yml index 52144ff..437d714 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -4,7 +4,7 @@ freebsd_instance: task: name: FreeBSD+ghc-9.10.3+cabal env: - BUILD_TYPE: cabal + PACKCHECK_COMMAND: cabal # ------------------------------------------------------------------------ # Common options @@ -45,11 +45,6 @@ task: CABAL_CHECK_RELAX: y CABAL_PROJECT: cabal.project - # ------------------------------------------------------------------------ - # Where to find the required tools - # ------------------------------------------------------------------------ - PATH: /usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/opt/curl/bin - # ------------------------------------------------------------------------ # Location of packcheck.sh (the shell script invoked to perform CI tests ). # ------------------------------------------------------------------------ @@ -63,6 +58,18 @@ task: PACKCHECK_GITHUB_URL: "https://raw.githubusercontent.com/composewell/packcheck" PACKCHECK_GITHUB_COMMIT: "fac7ab9aad48d5b80a1d65efb34ff353a848e927" + cabal_cache: + folder: ~/.cabal + fingerprint_script: cat *.cabal cabal.project 2>/dev/null || cat *.cabal + + ghcup_cache: + folder: ~/.ghcup + fingerprint_script: echo $GHCUP_VERSION $GHCVER + + #local_bin_cache: + # folder: ~/.local/bin + # fingerprint_script: echo $HLINT_VERSION + deps_install_script: | pkg install -y gmake pkg install -y bash @@ -83,4 +90,9 @@ task: fi packcheck_run_script: | - bash -c "$PACKCHECK $BUILD_TYPE" + PTH=/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin + # Use "bash -c" instead of invoking directly to preserve quoted + # arguments in PACKCHECK_COMMAND e.g. DOCSPEC_OPTIONS="--timeout 60". + # Direct invocation would word-split on spaces inside quoted values. + bash -c "$PACKCHECK $PACKCHECK_COMMAND PATH=$PTH" + diff --git a/.github/workflows/packcheck.yml b/.github/workflows/packcheck.yml index 3b4d7f7..9bc0a12 100644 --- a/.github/workflows/packcheck.yml +++ b/.github/workflows/packcheck.yml @@ -66,11 +66,6 @@ jobs: # ------------------------------------------------------------------------ CABAL_CHECK_RELAX: y - # ------------------------------------------------------------------------ - # Where to find the required tools - # ------------------------------------------------------------------------ - PATH: /sbin:/usr/sbin:/bin:/usr/bin - # ------------------------------------------------------------------------ # Location of packcheck.sh (the shell script invoked to perform CI tests ). # ------------------------------------------------------------------------ @@ -192,25 +187,51 @@ jobs: STACK_UPGRADE="y" DISABLE_SDIST_BUILD="y" + - name: ci + runner: windows-latest + command: cabal + ghc_version: 9.10.3 + pack_options: >- + CABAL_PROJECT=cabal.project + DISABLE_SDIST_BUILD="y" + steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 + - uses: actions/cache@v4 - name: Cache common directories + name: Cache common directories (non-Windows) + if: runner.os != 'Windows' with: + # On macos ghcup gets installed in ~/.ghcup + # On Linux it is /usr/local/.ghcup path: | - ~/.local + ~/.local/bin ~/.cabal ~/.stack ~/.ghcup + /usr/local/.ghcup + # Bump the key version to clear the cache + key: ${{ matrix.command }}-${{ matrix.ghc_version }}-${{ matrix.runner }} + + - uses: actions/cache@v4 + name: Cache common directories (Windows) + if: runner.os == 'Windows' + with: + path: | + ${{ env.APPDATA }}/local/bin + C:/ghcup + C:/cabal key: ${{ matrix.command }}-${{ matrix.ghc_version }}-${{ matrix.runner }} - name: Download packcheck + # on windows-latest GitHub Actions defaults to PowerShell + shell: bash run: | if test ! -e "$PACKCHECK" then if test -z "$PACKCHECK_GITHUB_COMMIT" then - die "PACKCHECK_GITHUB_COMMIT is not specified." + die "PACKCHECK_GITHUB_COMMIT env var is not specified." fi PACKCHECK_URL=${PACKCHECK_GITHUB_URL}/${PACKCHECK_GITHUB_COMMIT}/packcheck.sh curl --fail -sL -o "$PACKCHECK" $PACKCHECK_URL || exit 1 @@ -221,5 +242,16 @@ jobs: fi - name: Run packcheck + # on windows-latest GitHub Actions defaults to PowerShell + shell: bash run: | - bash -c "$PACKCHECK $PACKCHECK_COMMAND" + PATH_VAR=/sbin:/usr/sbin:/bin:/usr/bin + case "$(uname)" in + CYGWIN*|MINGW*|MSYS*) + PATH_VAR="$PATH_VAR:/c/Program Files/7-Zip:/mingw64/bin" + ;; + esac + # Use "bash -c" instead of invoking directly to preserve quoted + # arguments in PACKCHECK_COMMAND e.g. DOCSPEC_OPTIONS="--timeout 60". + # Direct invocation would word-split on spaces inside quoted values. + bash -c "$PACKCHECK $PACKCHECK_COMMAND PATH=\"$PATH_VAR\"" diff --git a/appveyor.yml b/appveyor.yml index d8fc349..662af77 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -32,17 +32,17 @@ environment: # DISABLE_TEST: "y" # DISABLE_BENCH: "y" # DISABLE_DOCS: "y" - DISABLE_DIST_CHECKS: "y" - DISABLE_SDIST_BUILD: "y" + # DISABLE_DIST_CHECKS: "y" + # DISABLE_SDIST_BUILD: "y" # Note: these require the "diff" utility. # DISABLE_SDIST_GIT_CHECK: "y" - # DISABLE_SDIST_PROJECT_CHECK: "y" + DISABLE_SDIST_PROJECT_CHECK: "y" # ------------------------------------------------------------------------ # cabal options # ------------------------------------------------------------------------ CABAL_CHECK_RELAX: "y" - CABAL_PROJECT: "cabal.project" + #CABAL_PROJECT: "cabal.project" # ------------------------------------------------------------------------ # Location of packcheck.sh (the shell script invoked to perform CI tests ). @@ -68,6 +68,7 @@ environment: cache: - "%APPDATA%\\local\\bin -> v1" - "%APPDATA%\\cabal" + - "%LOCALAPPDATA%\\cabal" - "C:\\ghcup" - "C:\\cabal" diff --git a/packcheck.sh b/packcheck.sh index cd7f22e..77d223e 100755 --- a/packcheck.sh +++ b/packcheck.sh @@ -89,21 +89,25 @@ which_cmd() { hash -r && type -P "$1" || true } -require_cmd () { +require_cmd_silent () { if test -z "$(which_cmd $1)" then echo "Required command [$1] not found in PATH [$PATH]." exit 1 - else - echo "Using [$1] at [$(which_cmd $1)]" fi } +require_cmd () { + require_cmd_silent $1 + echo "Using [$1] at [$(which_cmd $1)]" +} + # $1: command function run_verbose() { - # XXX redirecting to stderr leads to misaligned output because stdout and - # stdin are printed at different times. - echo "$*" 1>&2 + # Note: we used to redirect the echo to stderr but it leads to + # misaligned output because stdout and stdin are printed at different + # times. + echo "$*" bash -c "$*" } @@ -151,18 +155,20 @@ win_ungztar() { } set_os_specific_vars() { + require_cmd_silent uname local os=$(uname) case "$os" in Darwin|Linux|FreeBSD) OS_HAS_TOOLS=tar - OS_UNGZTAR_CMD="run_verbose_errexit tar xmzvf" + # Removed verbose from this, causes too much output when untar-ing sdist + OS_UNGZTAR_CMD="run_verbose_errexit tar xmzf" OS_LOCAL_DIR=.local OS_CABAL_DIR=.cabal OS_APP_HOME=$HOME ;; CYGWIN*|MINGW*|MSYS*) - require_cmd cygpath + require_cmd_silent cygpath require_envvar APPDATA - OS_HAS_TOOLS=cygpath + OS_HAS_TOOLS="7z cygpath" OS_UNGZTAR_CMD=win_ungztar OS_LOCAL_DIR=local OS_CABAL_DIR=cabal @@ -204,29 +210,61 @@ show_machine_info() { run_verbose mount || true show_step "Disk Usage" - run_verbose df -T || true ;; + run_verbose df -T || true + ;; FreeBSD) - echo "OS: FreeBSD" + run_verbose uname -a || true + run_verbose freebsd-version || true + show_step "CPU" + sysctl hw.model hw.ncpu hw.physicalcpu hw.logicalcpu 2>/dev/null || true + + show_step "Memory" + sysctl hw.physmem hw.realmem 2>/dev/null || true + run_verbose vmstat -H || true + show_step "Virtualization" + sysctl kern.vm_guest 2>/dev/null || true show_step "Filesystems" run_verbose mount || true show_step "Disk Usage" - run_verbose df || true ;; + run_verbose df -h || true + ;; Darwin) - echo "OS: MacOS" - + run_verbose uname -a || true + run_verbose sw_vers || true + show_step "Hardware/CPU" + sysctl -n hw.model + sysctl -n machdep.cpu.brand_string + sysctl hw.physicalcpu hw.logicalcpu hw.cpufrequency + show_step "Memory" + sysctl hw.memsize | awk '{printf "Total RAM: %.2f GB\n", $2/1024/1024/1024}' || true + run_verbose vm_stat || true show_step "Filesystems" run_verbose mount || true - show_step "Disk Usage" - run_verbose df -Y || true ;; + run_verbose df -h || true + ;; CYGWIN*|MINGW*|MSYS*) - echo "OS: Windows ($os)" ;; - *) die "OS: Unknown OS [$os]" ;; + echo "OS: Windows $WINVER ($os)" + + show_step "CPU" + echo "$NUMBER_OF_PROCESSORS processor(s)" + echo "${PROCESSOR_IDENTIFIER:-unknown}" + + show_step "Memory" + run_verbose free -h + + show_step "Disk Usage" + run_verbose df -h + ;; + + *) + die "OS: Unknown OS [$os]" + ;; esac } @@ -505,7 +543,7 @@ show_build_command() { echo "You can use the following command to reproduce this build:" echo - echo -n "$0 $BUILD " + echo -n "$(basename $0) $BUILD " for i in $SAFE_ENVVARS do local val="$(show_nonempty_var $i)" @@ -1008,6 +1046,18 @@ ghcup_install() { then run_verbose_errexit_with "cat ${GHCUP_PREFIX}/logs/*" \ $GHCUP_PATH install ghc $GHCUP_GHC_OPTIONS $tool_ver + + # Add the actual GHC bin dir to PATH so plain 'ghc' command without a + # version suffix is also available. Commands like "cabal path", "cabal + # info" require "ghc" in path. Other tasks like cabal build can use a + # specific versioned path like ghc-9.4.3. + local ghc_bindir + ghc_bindir=$($GHCUP_PATH whereis --directory ghc $tool_ver) + case "$(uname)" in + CYGWIN*|MINGW*|MSYS*) ghc_bindir=$(cygpath -u "$ghc_bindir") ;; + esac + echo "Adding GHC bin dir [$ghc_bindir] to PATH" + export PATH="$ghc_bindir:$PATH" else run_verbose_errexit ghcup install $tool $tool_ver fi @@ -1020,13 +1070,19 @@ ghcup_install() { ensure_default_ghc() { local ghc ghc="$(which_cmd ghc)" - if test -z "$ghc" -a -n "$GHCUP_VERSION" + if test -z "$ghc" then - echo "No default ghc found in PATH. Setting it using ghcup" - ghcup set ghc $GHCVER + HAVE_DEFAULT_GHC= + echo "WARNING! No default \"ghc\" found in PATH." + echo "WARNING! cabal path output will not be shown." + echo "WARNING! cabal info output will not be shown." + else + HAVE_DEFAULT_GHC=y fi } +# NOTE: this should be called before ensure_cabal as ensure_cabal needs +# default_ghc to run "cabal path". ensure_ghc() { local found local compiler @@ -1104,11 +1160,7 @@ ensure_ghc() { # GHCVER GHCVER=$($compiler --numeric-version) || exit 1 - # cabal info command requires "ghc" to be in PATH - if test -z "$DISABLE_SDIST_BUILD" - then - ensure_default_ghc - fi + ensure_default_ghc if test -n "$ENABLE_GHCJS" then @@ -1262,8 +1314,13 @@ ensure_cabal() { verlte $MIN_CABALVER $CABALVER || \ die "Cabal version should at least be $MIN_CABALVER" - echo - run_verbose $CABAL_BINARY_NAME path + if test -n "$HAVE_DEFAULT_GHC" + then + echo + run_verbose $CABAL_BINARY_NAME path + else + echo "WARNING! skipping \"cabal path\", \"ghc\" not in PATH" + fi } # Find a file in the parent/ancestor directories @@ -1358,26 +1415,11 @@ get_pkg_name() { get_pkg_full_name() { local pkgname - local full_name pkgname=$(get_pkg_name) || exit 1 - full_name=$($CABAL_BINARY_NAME info . | awk '{ if ($1 == "*") {print $2; exit}}') || true - if test -z "$full_name" - then - if test -z "$DISABLE_SDIST_BUILD" - then - echo - run_verbose pwd - run_verbose $CABAL_BINARY_NAME info . - die "'cabal info' command failed to determine package name.\nPlease use 'DISABLE_SDIST_BUILD=y' to avoid this issue." - fi - else - if test "${pkgname}${full_name#$pkgname}" != "${full_name}" - then - die "Inconsistent package name [$pkgname] and package full name [$full_name]" - fi - fi - - echo $full_name + local pkgver + pkgver=$(grep -i "^version:" "${pkgname}.cabal" | awk '{print $2}' | tr -d '\r') + test -n "$pkgver" || die "Could not determine package version from ${pkgname}.cabal" + echo "${pkgname}-${pkgver}" } # XXX use MULTI_PACKAGE_PROJECT as a CLI option, instead of trying to determine @@ -1474,20 +1516,6 @@ ensure_cabal_config() { fi } -# $1: dir where they are installed -remove_pkg_executables() { - test -n "$(which_cmd $CABAL_BINARY_NAME)" || return 0 - - exes=$($CABAL_BINARY_NAME info . | awk '{if ($1 == "Executables:") { print $2; exit }}') || exit 1 - echo "Remove installed binaries" - for i in $exes - do - # The executables may be under a cabal flag and may not have been installed - # unless we used that flag. So just use "rm -f" to remove silently. - run_verbose_errexit rm -f "$1"/"$i" - done -} - sdist_remove_project_file () { if test -e "$1" then @@ -1686,7 +1714,12 @@ then add them to .packcheck.ignore file at the root of the git repository." fi show_step "Package info [sdist $SDIST_OPTIONS]" - run_verbose $CABAL_BINARY_NAME info . || true + if test -n "$HAVE_DEFAULT_GHC" + then + run_verbose $CABAL_BINARY_NAME info . || true + else + echo "WARNING! skipping \"cabal info\", \"ghc\" not in PATH" + fi if test -f "./configure.ac" then @@ -1827,9 +1860,9 @@ build_hlint() { ensure_ghc stack_install_tool hlint else + ensure_ghc ensure_cabal ${LOCAL_BIN} ensure_cabal_config - ensure_ghc case "$BUILD" in cabal-v2) run_verbose_errexit $CABALCMD v2-install hlint ;; *) echo "Bug: unknown build type: $BUILD" ;; @@ -1970,11 +2003,9 @@ build_compile () { ensure_ghc && echo dont_need_cabal 'verbose' || ensure_cabal ${LOCAL_BIN} - # use the stack installed 7z instead. depends on ensure ghc where we setup - # stack paths. case "$(uname -s)" in MINGW*|MSYS*|CYGWIN*) - require_cmd 7z + require_cmd_silent 7z ;; esac @@ -2077,11 +2108,27 @@ get_rel_time() { # functions are returning non-zero exit codes. #set -e set -o pipefail -test -n "$BASE_TIME" || BASE_TIME=$(get_sys_time) test -n "$1" \ || { short_help; echo -e "\nTry --help for detailed help"; exit 1; } +# For commands make sure we call eval_env "$@" before doing anything else so +# that the envvars passed on the command line are evaluated first. We may have +# PATH as one of the args which we need to evaluate before running any +# command. +case $1 in + cabal|cabal-v2|stack|hlint|clean|cleanall|-h|--help|help|--version|version) BUILD=$1; shift; eval_env "$@" ;; + cabal-v1) die "cabal-v1 is not supported, please use cabal-v2 instead";; + cabal-new) die "cabal-new is not supported, please use cabal-v2 instead";; + *) echo -e "$(basename $0): unrecognized command [$1]\n" + short_help; exit 1 ;; +esac + +# This should be called after eval_env "$@" has been done above, so that PATH +# is set. +require_cmd_silent date +test -n "$BASE_TIME" || BASE_TIME=$(get_sys_time) + #------------------------------------------------------------------------------ # Need these to produce help @@ -2096,8 +2143,7 @@ HLINT_URL_PREFIX="https://github.com/ndmitchell/hlint/releases" DOCSPEC_PATH="${LOCAL_BIN}/cabal-docspec" DOCSPEC_URL_PREFIX="https://github.com/phadej/cabal-extras/releases/" -# XXX On windows this should be ghcup instead of .ghcup? See -# set_os_specific_vars +# See set_os_specific_vars case "$(uname)" in CYGWIN*|MINGW*|MSYS*) GHCUP_PREFIX=$(cygpath -u "${GHCUP_INSTALL_BASE_PREFIX:-C:}")/ghcup ;; @@ -2110,33 +2156,32 @@ GHCUP_URL_PREFIX="https://downloads.haskell.org/~ghcup" #------------------------------------------------------------------------------ -case $1 in - cabal) shift; eval_env "$@"; BUILD=cabal-v2;; - cabal-v1) die "cabal-v1 is not supported, please use cabal-v2 instead";; - cabal-new) die "cabal-new is not supported, please use cabal-v2 instead";; - cabal-v2) shift; eval_env "$@"; BUILD=cabal-v2;; - stack) shift; eval_env "$@"; BUILD=stack;; - hlint) shift; eval_env "$@"; BUILD=hlint;; +case $BUILD in + cabal) BUILD=cabal-v2;; clean) rm -rf .packcheck; exit;; cleanall) rm -rf .packcheck .stack-work exit;; - -h | --help | help) show_help; exit;; - --version) show_version; exit;; - *) echo -e "Error: First argument must be a command\n" - short_help; exit 1 ;; + -h|--help|help) show_help; exit;; + --version|version) show_version; exit;; esac test -z "$CHECK_ENV" || check_boolean_var CHECK_ENV test -z "$CHECK_ENV" || check_clean_env +require_cmd_silent bash echo bash --version show_step "Build command" show_build_command -TOOLS="awk cat curl cut date env head mkdir printf rm sleep tr which sort \ +# Note that date, uname, cygpath, rm are used even before this point. This +# informatory so that PATH argument can be cleaned up by looking at where the +# tools are. Anything used in require_cmd can be listed here. But the danger is +# that we will put a requirement on a tool even if it may not be required in a +# particular command's flow. +TOOLS="awk cat curl cut date env head mkdir printf rm sleep sort tr uname which \ $OS_HAS_TOOLS" show_step "Check basic tools"