From 24beb12f44616ee436afe3f6c7cbdd20a34cf70f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Facundo=20Dom=C3=ADnguez?= Date: Fri, 12 Feb 2021 14:01:21 +0000 Subject: [PATCH 1/6] Pass toolchain info to cabal_wrapper when calling it. --- haskell/cabal.bzl | 35 +++++++++++++++++++++- haskell/cabal_wrapper.bzl | 39 ++---------------------- haskell/private/cabal_wrapper.py.tpl | 44 ++++++++++++++++++---------- 3 files changed, 65 insertions(+), 53 deletions(-) diff --git a/haskell/cabal.bzl b/haskell/cabal.bzl index a29ef83de..5329c8690 100644 --- a/haskell/cabal.bzl +++ b/haskell/cabal.bzl @@ -6,7 +6,7 @@ load("@bazel_tools//tools/build_defs/repo:utils.bzl", "maybe") 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") @@ -122,10 +122,38 @@ def _concat(sequences): def _uniquify(xs): return depset(xs).to_list() +def _cabal_toolchain_info(ctx, hs): + """Yields a struct containing the toolchain information needed by the cabal wrapper""" + 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" + + cc = hs_toolchain.cc_wrapper.executable.path + return struct( + 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 = hs.toolchain.is_windows, + workspace = ctx.workspace_name, + ghc_cc_args = ghc_cc_program_args("$CC"), + ) + def _prepare_cabal_inputs( hs, cc, posix, + toolchain_info, dep_info, cc_info, direct_cc_info, @@ -288,6 +316,7 @@ def _prepare_cabal_inputs( runghc_args = runghc_args, extra_args = extra_args, path_args = path_args, + toolchain_info = toolchain_info, ) inputs = depset( @@ -422,10 +451,12 @@ def _haskell_cabal_library_impl(ctx): sibling = cabal, ) (tool_inputs, tool_input_manifests) = ctx.resolve_tools(tools = ctx.attr.tools) + toolchain_info = _cabal_toolchain_info(ctx, hs) c = _prepare_cabal_inputs( hs, cc, posix, + toolchain_info, dep_info, cc_info, direct_cc_info, @@ -709,10 +740,12 @@ def _haskell_cabal_binary_impl(ctx): sibling = cabal, ) (tool_inputs, tool_input_manifests) = ctx.resolve_tools(tools = ctx.attr.tools) + toolchain_info = _cabal_toolchain_info(ctx, hs) c = _prepare_cabal_inputs( hs, cc, posix, + toolchain_info, dep_info, cc_info, direct_cc_info, diff --git a/haskell/cabal_wrapper.bzl b/haskell/cabal_wrapper.bzl index 3b26ae5bf..e392f8dd5 100644 --- a/haskell/cabal_wrapper.bzl +++ b/haskell/cabal_wrapper.bzl @@ -4,44 +4,17 @@ 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( + cabal_wrapper = ctx.actions.declare_file("cabal_wrapper.py") + ctx.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( @@ -51,15 +24,7 @@ _cabal_wrapper = rule( 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): diff --git a/haskell/private/cabal_wrapper.py.tpl b/haskell/private/cabal_wrapper.py.tpl index 51ead953d..d31dc6f42 100755 --- a/haskell/private/cabal_wrapper.py.tpl +++ b/haskell/private/cabal_wrapper.py.tpl @@ -15,6 +15,17 @@ # , "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 @@ -36,6 +47,9 @@ verbose = os.environ.get("CABAL_VERBOSE", "") == "True" 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) @@ -53,16 +67,16 @@ 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 = r.Rlocation(toolchain_info["workspace"] + "/" + exe) + if not os.path.isfile(path) and is_windows: + path = r.Rlocation(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([ @@ -93,17 +107,17 @@ haddockdir = os.path.join(pkgroot, "{}_haddock".format(name)) 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]) @@ -124,7 +138,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:"): @@ -138,7 +152,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 @@ -164,7 +178,7 @@ with tmpdir() as distdir: "--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 @@ -172,7 +186,7 @@ with tmpdir() as distdir: # 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, \ From 8df3251baf3f6b8469d149643b9e87cae83b5622 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Facundo=20Dom=C3=ADnguez?= Date: Fri, 12 Feb 2021 14:10:44 +0000 Subject: [PATCH 2/6] Convert the cabal_wrapper template into a python source file. --- haskell/BUILD.bazel | 2 +- haskell/cabal.bzl | 1 + haskell/cabal_wrapper.bzl | 32 +------------------ ...{cabal_wrapper.py.tpl => cabal_wrapper.py} | 0 4 files changed, 3 insertions(+), 32 deletions(-) rename haskell/private/{cabal_wrapper.py.tpl => cabal_wrapper.py} (100%) diff --git a/haskell/BUILD.bazel b/haskell/BUILD.bazel index c07f35c33..8bdd99223 100644 --- a/haskell/BUILD.bazel +++ b/haskell/BUILD.bazel @@ -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", diff --git a/haskell/cabal.bzl b/haskell/cabal.bzl index 5329c8690..4deb4a72b 100644 --- a/haskell/cabal.bzl +++ b/haskell/cabal.bzl @@ -306,6 +306,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, diff --git a/haskell/cabal_wrapper.bzl b/haskell/cabal_wrapper.bzl index e392f8dd5..c7fc8cab2 100644 --- a/haskell/cabal_wrapper.bzl +++ b/haskell/cabal_wrapper.bzl @@ -1,39 +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): - cabal_wrapper_tpl = ctx.file._cabal_wrapper_tpl - cabal_wrapper = ctx.actions.declare_file("cabal_wrapper.py") - ctx.actions.expand_template( - template = cabal_wrapper_tpl, - output = cabal_wrapper, - is_executable = True, - substitutions = { - }, - ) - return [DefaultInfo( - files = depset([cabal_wrapper]), - )] - -_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"), - ), - }, -) - 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 = [ diff --git a/haskell/private/cabal_wrapper.py.tpl b/haskell/private/cabal_wrapper.py similarity index 100% rename from haskell/private/cabal_wrapper.py.tpl rename to haskell/private/cabal_wrapper.py From dd651a3d5f6b6ec8d40c4933f2a75e4362e1653f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Facundo=20Dom=C3=ADnguez?= Date: Fri, 12 Feb 2021 18:18:45 +0000 Subject: [PATCH 3/6] Remove uses or Rlocation in the cabal_wrapper --- haskell/private/cabal_wrapper.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/haskell/private/cabal_wrapper.py b/haskell/private/cabal_wrapper.py index d31dc6f42..aa7615230 100755 --- a/haskell/private/cabal_wrapper.py +++ b/haskell/private/cabal_wrapper.py @@ -30,7 +30,6 @@ 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 @@ -70,10 +69,9 @@ def find_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(toolchain_info["workspace"] + "/" + exe) + path = toolchain_info["workspace"] + "/" + exe if not os.path.isfile(path) and is_windows: - path = r.Rlocation(toolchain_info["workspace"] + "/" + exe + ".exe") + path = toolchain_info["workspace"] + "/" + exe + ".exe" return path path_list_sep = ";" if is_windows else ":" From b213994e77561c2ca9ccc9b42cedca1ff0145a66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Facundo=20Dom=C3=ADnguez?= Date: Fri, 12 Feb 2021 19:01:19 +0000 Subject: [PATCH 4/6] Use the cc provided by cc_interop_info with the cabal_wrapper --- haskell/cabal.bzl | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/haskell/cabal.bzl b/haskell/cabal.bzl index 4deb4a72b..f94fa91c1 100644 --- a/haskell/cabal.bzl +++ b/haskell/cabal.bzl @@ -122,30 +122,27 @@ def _concat(sequences): def _uniquify(xs): return depset(xs).to_list() -def _cabal_toolchain_info(ctx, hs): +def _cabal_toolchain_info(hs, cc, workspace_name): """Yields a struct containing the toolchain information needed by the cabal wrapper""" - 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 + ar = cc.tools.ar if ar.find("libtool") >= 0: ar = "/usr/bin/ar" - cc = hs_toolchain.cc_wrapper.executable.path return struct( 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, + cc = cc.tools.cc, + strip = cc.tools.strip, is_windows = hs.toolchain.is_windows, - workspace = ctx.workspace_name, + workspace = workspace_name, ghc_cc_args = ghc_cc_program_args("$CC"), ) @@ -153,7 +150,7 @@ def _prepare_cabal_inputs( hs, cc, posix, - toolchain_info, + workspace_name, dep_info, cc_info, direct_cc_info, @@ -317,7 +314,7 @@ def _prepare_cabal_inputs( runghc_args = runghc_args, extra_args = extra_args, path_args = path_args, - toolchain_info = toolchain_info, + toolchain_info = _cabal_toolchain_info(hs, cc, workspace_name), ) inputs = depset( @@ -452,12 +449,11 @@ def _haskell_cabal_library_impl(ctx): sibling = cabal, ) (tool_inputs, tool_input_manifests) = ctx.resolve_tools(tools = ctx.attr.tools) - toolchain_info = _cabal_toolchain_info(ctx, hs) c = _prepare_cabal_inputs( hs, cc, posix, - toolchain_info, + ctx.workspace_name, dep_info, cc_info, direct_cc_info, @@ -741,12 +737,11 @@ def _haskell_cabal_binary_impl(ctx): sibling = cabal, ) (tool_inputs, tool_input_manifests) = ctx.resolve_tools(tools = ctx.attr.tools) - toolchain_info = _cabal_toolchain_info(ctx, hs) c = _prepare_cabal_inputs( hs, cc, posix, - toolchain_info, + ctx.workspace_name, dep_info, cc_info, direct_cc_info, From 26b6f9bbd1d4e2f18165a89f9ef8b80638804383 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Facundo=20Dom=C3=ADnguez?= Date: Mon, 15 Feb 2021 14:22:35 -0300 Subject: [PATCH 5/6] Clear RUNFILES_DIR and RUNFILES_MANIFEST_FILE in cabal_wrapper.py --- haskell/private/cabal_wrapper.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/haskell/private/cabal_wrapper.py b/haskell/private/cabal_wrapper.py index aa7615230..f155695e2 100755 --- a/haskell/private/cabal_wrapper.py +++ b/haskell/private/cabal_wrapper.py @@ -164,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, \ From 3306f536d033ea0859688fa085eba298065ab82a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Facundo=20Dom=C3=ADnguez?= Date: Wed, 17 Feb 2021 16:47:01 -0300 Subject: [PATCH 6/6] Set RUNFILES_MANIFEST_FILE in cc-wrapper for windows if not set. --- haskell/private/cc_wrapper_windows.sh.tpl | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/haskell/private/cc_wrapper_windows.sh.tpl b/haskell/private/cc_wrapper_windows.sh.tpl index e62c07a5a..63eb2e280 100644 --- a/haskell/private/cc_wrapper_windows.sh.tpl +++ b/haskell/private/cc_wrapper_windows.sh.tpl @@ -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