Skip to content

Commit

Permalink
Merge pull request #1493 from tweag/fd/runtime-toolchain-info
Browse files Browse the repository at this point in the history
Pass toolchain info to the cabal_wrapper at the call site
  • Loading branch information
facundominguez committed Feb 18, 2021
2 parents 7fd2f19 + cad9476 commit b977be3
Show file tree
Hide file tree
Showing 5 changed files with 81 additions and 85 deletions.
2 changes: 1 addition & 1 deletion haskell/BUILD.bazel
Expand Up @@ -21,7 +21,7 @@ exports_files(
glob(["*.bzl"]) + [
"assets/ghci_script",
"private/cabal_wrapper.sh.tpl",
"private/cabal_wrapper.py.tpl",
"private/cabal_wrapper.py",
"private/coverage_wrapper.sh.tpl",
"private/ghci_repl_wrapper.sh",
"private/haddock_wrapper.sh.tpl",
Expand Down
31 changes: 30 additions & 1 deletion haskell/cabal.bzl
Expand Up @@ -6,7 +6,7 @@ load("@bazel_tools//tools/build_defs/repo:utils.bzl", "maybe", "read_netrc", "us
load("//vendor/bazel_json/lib:json_parser.bzl", "json_parse")
load("@bazel_tools//tools/cpp:lib_cc_configure.bzl", "get_cpu_value")
load("@bazel_tools//tools/cpp:toolchain_utils.bzl", "find_cpp_toolchain")
load(":cc.bzl", "cc_interop_info")
load(":cc.bzl", "cc_interop_info", "ghc_cc_program_args")
load(":private/actions/info.bzl", "library_info_output_groups")
load(":private/context.bzl", "haskell_context", "render_env")
load(":private/dependencies.bzl", "gather_dep_info")
Expand Down Expand Up @@ -153,10 +153,35 @@ def _concat(sequences):
def _uniquify(xs):
return depset(xs).to_list()

def _cabal_toolchain_info(hs, cc, workspace_name):
"""Yields a struct containing the toolchain information needed by the cabal wrapper"""

# If running on darwin but XCode is not installed (i.e., only the Command
# Line Tools are available), then Bazel will make ar_executable point to
# "/usr/bin/libtool". Since we call ar directly, override it.
# TODO: remove this if Bazel fixes its behavior.
# Upstream ticket: https://github.com/bazelbuild/bazel/issues/5127.
ar = cc.tools.ar
if ar.find("libtool") >= 0:
ar = "/usr/bin/ar"

return struct(
ghc = hs.tools.ghc.path,
ghc_pkg = hs.tools.ghc_pkg.path,
runghc = hs.tools.runghc.path,
ar = ar,
cc = cc.tools.cc,
strip = cc.tools.strip,
is_windows = hs.toolchain.is_windows,
workspace = workspace_name,
ghc_cc_args = ghc_cc_program_args("$CC"),
)

def _prepare_cabal_inputs(
hs,
cc,
posix,
workspace_name,
dep_info,
cc_info,
direct_cc_info,
Expand Down Expand Up @@ -309,6 +334,7 @@ def _prepare_cabal_inputs(

# Redundant with _binary_paths() above, but better be explicit when we can.
path_args.extend([_cabal_tool_flag(tool_flag) for tool_flag in tool_inputs.to_list() if _cabal_tool_flag(tool_flag)])

args = struct(
component = component,
pkg_name = package_id,
Expand All @@ -319,6 +345,7 @@ def _prepare_cabal_inputs(
runghc_args = runghc_args,
extra_args = extra_args,
path_args = path_args,
toolchain_info = _cabal_toolchain_info(hs, cc, workspace_name),
)

inputs = depset(
Expand Down Expand Up @@ -457,6 +484,7 @@ def _haskell_cabal_library_impl(ctx):
hs,
cc,
posix,
ctx.workspace_name,
dep_info,
cc_info,
direct_cc_info,
Expand Down Expand Up @@ -744,6 +772,7 @@ def _haskell_cabal_binary_impl(ctx):
hs,
cc,
posix,
ctx.workspace_name,
dep_info,
cc_info,
direct_cc_info,
Expand Down
67 changes: 1 addition & 66 deletions haskell/cabal_wrapper.bzl
@@ -1,74 +1,9 @@
load(":private/context.bzl", "haskell_context", "render_env")
load(":cc.bzl", "cc_interop_info", "ghc_cc_program_args")
load("@bazel_tools//tools/cpp:toolchain_utils.bzl", "find_cpp_toolchain")
load("@rules_python//python:defs.bzl", "py_binary")

def _cabal_wrapper_impl(ctx):
hs = haskell_context(ctx)
hs_toolchain = ctx.toolchains["@rules_haskell//haskell:toolchain"]
cc_toolchain = find_cpp_toolchain(ctx)

# If running on darwin but XCode is not installed (i.e., only the Command
# Line Tools are available), then Bazel will make ar_executable point to
# "/usr/bin/libtool". Since we call ar directly, override it.
# TODO: remove this if Bazel fixes its behavior.
# Upstream ticket: https://github.com/bazelbuild/bazel/issues/5127.
ar = cc_toolchain.ar_executable
if ar.find("libtool") >= 0:
ar = "/usr/bin/ar"

cabal_wrapper_tpl = ctx.file._cabal_wrapper_tpl
cabal_wrapper = hs.actions.declare_file("cabal_wrapper.py")
cc = hs_toolchain.cc_wrapper.executable.path
hs.actions.expand_template(
template = cabal_wrapper_tpl,
output = cabal_wrapper,
is_executable = True,
substitutions = {
"%{ghc}": hs.tools.ghc.path,
"%{ghc_pkg}": hs.tools.ghc_pkg.path,
"%{runghc}": hs.tools.runghc.path,
"%{ar}": ar,
"%{cc}": cc,
"%{strip}": cc_toolchain.strip_executable,
"%{is_windows}": str(hs.toolchain.is_windows),
"%{workspace}": ctx.workspace_name,
"%{ghc_cc_args}": repr(ghc_cc_program_args("$CC")),
},
)
return [DefaultInfo(
files = depset([cabal_wrapper]),
runfiles = hs.toolchain.cc_wrapper.runfiles.merge(ctx.runfiles(
transitive_files = cc_toolchain.all_files,
collect_data = True,
)),
)]

_cabal_wrapper = rule(
implementation = _cabal_wrapper_impl,
attrs = {
"_cabal_wrapper_tpl": attr.label(
allow_single_file = True,
default = Label("@rules_haskell//haskell:private/cabal_wrapper.py.tpl"),
),
"_cc_toolchain": attr.label(
default = Label("@bazel_tools//tools/cpp:current_cc_toolchain"),
),
},
toolchains = [
"@bazel_tools//tools/cpp:toolchain_type",
"@rules_haskell//haskell:toolchain",
],
fragments = ["cpp"],
)

def cabal_wrapper(name, **kwargs):
_cabal_wrapper(
name = name + ".py",
)
py_binary(
name = name,
srcs = [name + ".py"],
srcs = ["@rules_haskell//haskell:private/cabal_wrapper.py"],
srcs_version = "PY3",
python_version = "PY3",
deps = [
Expand Down
Expand Up @@ -15,11 +15,21 @@
# , "runghc_args": list of string # Arguments for runghc
# , "extra_args": list of string # Additional args to Setup.hs configure.
# , "path_args": list of string # Additional args to Setup.hs configure where paths need to be prefixed with execroot.
# , "toolchain_info" :
# { "ghc": string # path to ghc
# , "ghc_pkg": string # path to ghc_pkg
# , "runghc": string # path to runghc
# , "ar": string # path to ar
# , "cc": string # path to cc
# , "strip": string # path to strip
# , "is_windows": boolean # this is a windows build
# , "workspace": string # workspace name
# , "ghc_cc_args": list of string # cc flags for ghc
# }
# }

from __future__ import print_function

from bazel_tools.tools.python.runfiles import runfiles as bazel_runfiles
from contextlib import contextmanager
from glob import glob
import json
Expand All @@ -36,6 +46,9 @@
with open(sys.argv.pop(1)) as json_file:
json_args = json.load(json_file)

toolchain_info = json_args["toolchain_info"]
is_windows = toolchain_info["is_windows"]

def run(cmd, *args, **kwargs):
if debug:
print("+ " + " ".join(["'{}'".format(arg) for arg in cmd]), file=sys.stderr)
Expand All @@ -53,16 +66,15 @@ def run(cmd, *args, **kwargs):
def find_exe(exe):
if os.path.isfile(exe):
path = os.path.abspath(exe)
elif "%{is_windows}" == "True" and os.path.isfile(exe + ".exe"):
elif is_windows and os.path.isfile(exe + ".exe"):
path = os.path.abspath(exe + ".exe")
else:
r = bazel_runfiles.Create()
path = r.Rlocation("%{workspace}/" + exe)
if not os.path.isfile(path) and "%{is_windows}" == "True":
path = r.Rlocation("%{workspace}/" + exe + ".exe")
path = toolchain_info["workspace"] + "/" + exe
if not os.path.isfile(path) and is_windows:
path = toolchain_info["workspace"] + "/" + exe + ".exe"
return path

path_list_sep = ";" if "%{is_windows}" == "True" else ":"
path_list_sep = ";" if is_windows else ":"

def canonicalize_path(path):
return path_list_sep.join([
Expand Down Expand Up @@ -93,17 +105,17 @@ def canonicalize_path(path):
htmldir = os.path.join(pkgroot, "{}_haddock_html".format(name))
runghc_args = json_args["runghc_args"]

runghc = find_exe(r"%{runghc}")
ghc = find_exe(r"%{ghc}")
ghc_pkg = find_exe(r"%{ghc_pkg}")
runghc = find_exe(toolchain_info["runghc"])
ghc = find_exe(toolchain_info["ghc"])
ghc_pkg = find_exe(toolchain_info["ghc_pkg"])

extra_args = json_args["extra_args"]

path_args = json_args["path_args"]

ar = find_exe("%{ar}")
cc = find_exe("%{cc}")
strip = find_exe("%{strip}")
ar = find_exe(toolchain_info["ar"])
cc = find_exe(toolchain_info["cc"])
strip = find_exe(toolchain_info["strip"])

def recache_db():
run([ghc_pkg, "recache", "--package-db=" + package_database])
Expand All @@ -124,7 +136,7 @@ def tmpdir():
#
# On Windows we don't do dynamic linking and prefer shorter paths to avoid
# exceeding `MAX_PATH`.
if "%{is_windows}" == "True":
if is_windows:
distdir = tempfile.mkdtemp()
else:
if component.startswith("exe:"):
Expand All @@ -138,7 +150,7 @@ def tmpdir():

with tmpdir() as distdir:
enable_relocatable_flags = ["--enable-relocatable"] \
if "%{is_windows}" != "True" else []
if not is_windows else []

# Cabal really wants the current working directory to be directory
# where the .cabal file is located. So we have no choice but to chance
Expand All @@ -152,6 +164,22 @@ def tmpdir():
os.putenv("TMP", os.path.join(distdir, "tmp"))
os.putenv("TEMP", os.path.join(distdir, "tmp"))
os.makedirs(os.path.join(distdir, "tmp"))
# XXX: Bazel hack
# When cabal_wrapper calls other tools with runfiles, the runfiles are
# searched in the runfile tree of cabal_wrapper unless we clear
# RUNFILES env vars. After clearing the env vars, each tool looks for
# runfiles in its own runfiles tree.
#
# Clearing RUNFILES_DIR is necessary in macos where a wrapper script
# cc-wrapper.sh is used from the cc toolchain.
#
# Clearing RUNFILES_MANIFEST_FILE is necessary in windows where we
# use a wrapper script cc-wrapper-bash.exe which has a different
# manifest file than cabal_wrapper.py.
if "RUNFILES_DIR" in os.environ:
del os.environ["RUNFILES_DIR"]
if "RUNFILES_MANIFEST_FILE" in os.environ:
del os.environ["RUNFILES_MANIFEST_FILE"]
runghc_args = [arg.replace("./", execroot + "/") for arg in runghc_args]
run([runghc] + runghc_args + [setup, "configure", \
component, \
Expand All @@ -164,15 +192,15 @@ def tmpdir():
"--with-strip=" + strip,
"--enable-deterministic", \
] +
[ "--ghc-option=" + flag.replace("$CC", cc) for flag in %{ghc_cc_args} ] +
[ "--ghc-option=" + flag.replace("$CC", cc) for flag in toolchain_info["ghc_cc_args"] ] +
enable_relocatable_flags + \
[ \
# Make `--builddir` a relative path. Using an absolute path would
# confuse the `RUNPATH` patching logic in `cc_wrapper`. It assumes that
# absolute paths refer the temporary directory that GHC uses for
# intermediate template Haskell outputs. `cc_wrapper` should improved
# in that regard.
"--builddir=" + (os.path.relpath(distdir) if "%{is_windows}" != "True" else distdir), \
"--builddir=" + (os.path.relpath(distdir) if not is_windows else distdir), \
"--prefix=" + pkgroot, \
"--libdir=" + libdir, \
"--dynlibdir=" + dynlibdir, \
Expand Down
4 changes: 4 additions & 0 deletions haskell/private/cc_wrapper_windows.sh.tpl
Expand Up @@ -65,6 +65,10 @@ find_exe() {
{ echo>&2 "ERROR: cannot find $f"; exit 1; }; f=; set -e
# --- end runfiles.bash initialization v2 ---

# The RUNFILES_MANIFEST_FILE needs to be set explicitly
# when cc_wrapper is invoked from the cabal_wrapper
RUNFILES_MANIFEST_FILE="${RUNFILES_MANIFEST_FILE:-"$0.exe.runfiles_manifest"}"

location="$(rlocation "{:workspace:}/$exe")"
if [[ -f "$location" ]]; then
return
Expand Down

0 comments on commit b977be3

Please sign in to comment.