#!/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 "8.10.7"). GHC_VERSION=${GHC_VERSION:="8.10.7"} readonly MIN_BAZEL_MAJOR=4 readonly MIN_BAZEL_MINOR=0 readonly MAX_BAZEL_MAJOR=5 readonly MAX_BAZEL_MINOR=2 stderr () { >&2 echo "$*" } usage () { exit_code="$1" cat >&2 <<-"EOF" start [--use-bindists|--use-nix|--help] 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. 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= PRINT_NIX_USAGE= parse_args () { # Defaults, if no arguments provided if [ "$#" -eq 0 ]; then MODE="bindists" PRINT_NIX_USAGE=1 return fi case "$1" in "--help") usage 0 ;; "--use-bindists") MODE="bindists" ;; "--use-nix") MODE="nix" ;; *) usage 1 ;; esac } 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 zlib.BUILD.bazel Example.hs; 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}" ## Write WORKSPACE File ################################################ stderr "Creating WORKSPACE" cat > WORKSPACE <> WORKSPACE ## 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=@io_tweag_rules_nixpkgs//nixpkgs/platforms:host run --host_platform=@io_tweag_rules_nixpkgs//nixpkgs/platforms:host EOF fi cat >> .bazelrc < BUILD.bazel < Example.hs <&2 <