#!/usr/bin/env sh # # Checks the version of Bazel found in the PATH, and then initializes # a new Bazel workspace with dummy Haskell build targets. set -eu # If the environment variable `GHC_VERSION` is not set yet, # we use the default version (currently "9.2.8"). GHC_VERSION=${GHC_VERSION:="9.2.8"} readonly MIN_BAZEL_MAJOR=6 readonly MIN_BAZEL_MINOR=0 readonly MAX_BAZEL_MAJOR=6 readonly MAX_BAZEL_MINOR=4 stderr () { >&2 echo "$*" } usage () { exit_code="$1" cat >&2 <<-"EOF" start [--use-bindists|--use-nix|--help|--with-bzlmod=true|false] Set up a minimal rules_haskell bazel configuration. --use-bindists: The project is set up to provision GHC from binary distributions. This does not require nix to build. --use-nix: The project is set up to provision GHC from nixpkgs. This requires nix to build. --with-bzlmod=true|false: If enabled, use a MODULE.bazel file and enable bzlmod to fetch rules_haskell. (only supported in bindist mode) If no argument is given, `--use-bindists` is assumed and a helpful message is printed that `--use-nix` also exists. For more information visit https://haskell.build/ EOF exit "${exit_code}" } # either bindists or nix MODE="bindists" PRINT_NIX_USAGE= BZLMOD=false parse_args () { # Defaults, if no arguments provided if [ "$#" -eq 0 ]; then PRINT_NIX_USAGE=1 return fi for arg; do case "$arg" in "--help") usage 0 ;; "--use-bindists") MODE="bindists" ;; "--use-nix") MODE="nix" ;; "--with-bzlmod=true") BZLMOD=true ;; "--with-bzlmod=false") BZLMOD=false ;; *) usage 1 ;; esac done if $BZLMOD && [ $MODE != bindists ]; then stderr "error: --with-bzlmod is only supported with --use-bindists" exit 1 fi } check_dir () { path="$1" # we can't write a file when a directory of that name exists if [ -d "${path}" ]; then stderr "STOP: There's a directory named ${path} but we want to write a file with the same name. Please delete or rename the ${path} directory." exit 1 fi } check_alt () { path="$1" alternative="$2" check_dir "${alternative}" if [ -f "${path}" ]; then stderr "STOP: There's a ${path} file but we intend to write a ${alternative} file. When both exist, bazel will pick the one with the .bazel extension, ignoring the other one. Please delete the ${path} file." exit 1 fi } check_clash () { path="$1" check_dir "${path}" if [ -e "${path}" ]; then stderr "STOP: The current directory already has a ${path} file and we don't want to overwrite it." exit 1 fi } check_files_dont_exist () { # A BUILD.bazel file takes precedence over a BUILD file and likewise # a WORKSPACE.bazel file takes precedence over a WORKSPACE file. We write # BUILD.bazel and WORKSPACE files. # Some Bazel tooling may fail on WORKSPACE.bazel files, # e.g. https://github.com/bazelbuild/bazel-gazelle/issues/678 check_alt WORKSPACE.bazel WORKSPACE check_alt BUILD BUILD.bazel for clash in .bazelrc WORKSPACE BUILD.bazel MODULE.bazel zlib.BUILD.bazel Example.hs non_module_deps.bzl; do check_clash "${clash}" done } check_bazel_version () { actual_raw=$(bazel version | grep -E '^Build label:' | grep -Eo '[0-9.]+') # shellcheck disable=SC2034 IFS=. read -r actual_major actual_minor actual_patch <<-EOF ${actual_raw} EOF expected_min="${MIN_BAZEL_MAJOR}.${MIN_BAZEL_MINOR}.0" expected_max="${MAX_BAZEL_MAJOR}.${MAX_BAZEL_MINOR}.x" if [ "${actual_major}" -gt "${MAX_BAZEL_MAJOR}" ] || \ { [ "${actual_major}" -eq "${MAX_BAZEL_MAJOR}" ] && [ "${actual_minor}" -gt "${MAX_BAZEL_MINOR}" ]; } then stderr "Warning: a too new version of Bazel detected: v${actual_raw}." stderr " Recommended versions are from v${expected_min} to v${expected_max}." elif [ "${actual_major}" -lt "${MIN_BAZEL_MAJOR}" ] || \ { [ "${actual_major}" -eq "${MIN_BAZEL_MAJOR}" ] && [ "${actual_minor}" -lt "${MIN_BAZEL_MINOR}" ]; } then stderr "Error: Need at least Bazel v${expected_min} but v${actual_raw} detected." exit 1 fi } ## Parse Arguments and Preflight Checks ################################ parse_args "$@" if [ "${PRINT_NIX_USAGE}" ]; then # shellcheck disable=SC2016 stderr 'INFO: Creating a WORKSPACE file based on GHC bindists. If you want to use a nix-based setup (e.g. on NixOS), call with `--use-nix`. See `--help` for more info.' fi check_files_dont_exist check_bazel_version ## Write zlib Build File ############################################### readonly ZLIB_BUILD_FILE="zlib.BUILD.bazel" stderr "Creating ${ZLIB_BUILD_FILE}" case "${MODE}" in "bindists") cat <<-EOF load("@rules_cc//cc:defs.bzl", "cc_library") cc_library( name = "zlib", # Import ':z' as 'srcs' to enforce the library name 'libz.so'. Otherwise, # Bazel would mangle the library name and e.g. Cabal wouldn't recognize it. srcs = [":z"], hdrs = glob(["*.h"]), includes = ["."], visibility = ["//visibility:public"], ) cc_library( name = "z", srcs = glob(["*.c"]), hdrs = glob(["*.h"]), copts = ["-Wno-implicit-function-declaration"], ) EOF ;; "nix") cat <<-EOF load("@rules_cc//cc:defs.bzl", "cc_library") filegroup( name = "include", srcs = glob(["include/*.h"]), visibility = ["//visibility:public"], ) cc_library( name = "zlib", srcs = ["@nixpkgs_zlib//:lib"], hdrs = [":include"], strip_include_prefix = "include", visibility = ["//visibility:public"], ) EOF ;; esac > "${ZLIB_BUILD_FILE}" SNAPSHOT=lts-20.3 case "$GHC_VERSION" in 8.10.*) SNAPSHOT=lts-18.28 ;; 9.0.*) SNAPSHOT=lts-19.33 ;; 9.2.*) SNAPSHOT=lts-20.26 ;; 9.4.*) SNAPSHOT=lts-21.5 ;; *) stderr "warning: unsupported GHC version: ${GHC_VERSION}, using stack resolver ${SNAPSHOT}" esac ## Write WORKSPACE File ################################################ stderr "Creating WORKSPACE" if $BZLMOD; then touch WORKSPACE else cat > WORKSPACE <> WORKSPACE fi if $BZLMOD; then stderr "Creating MODULE.bazel" cat >MODULE.bazel <non_module_deps.bzl <<-EOF load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") def _non_module_deps_impl(_mctx): http_archive( name = "zlib.dev", build_file = "//:${ZLIB_BUILD_FILE}", sha256 = "b5b06d60ce49c8ba700e0ba517fa07de80b5d4628a037f4be8ad16955be7a7c0", strip_prefix = "zlib-1.3", urls = ["https://github.com/madler/zlib/archive/v1.3.tar.gz"], ) non_module_deps = module_extension(implementation = _non_module_deps_impl) EOF fi ## Write .bazelrc File ################################################# stderr "Creating .bazelrc" cat > .bazelrc <> .bazelrc <<-EOF # This project uses a GHC provisioned via nix. # We need to use the rules_haskell nix toolchain accordingly: build --host_platform=@rules_nixpkgs_core//platforms:host run --host_platform=@rules_nixpkgs_core//platforms:host EOF fi cat >> .bazelrc < BUILD.bazel < Example.hs <&2 <