From 7f16f6e36f72bafc92abbe7d9cfd3da6f897506e Mon Sep 17 00:00:00 2001 From: Mathieu Boespflug Date: Sat, 12 Jan 2019 14:13:06 +0100 Subject: [PATCH] Refactoring: remove visible-binaries To precisely control what binaries are available during compilation, we used to create a single directory called `visible-binaries`, in which we put all the binaries (ghc, haddock, etc) and shell commands (cat, mkdir, etc) that we needed. There was another point to this: build actions could call any binary and that binary could in turn exec any other binary, since `visible-binaries` was in the `PATH`. In this commit, we do away with that. The reason is that there are downsides to this approach: * there is little value in trying to be more hermetic than Bazel is when it comes to shell commands; * all build actions were reusing the same `PATH`, so we were actually exposing more binaries than strictly necessary (each action needs a different subset); * the logic to create `visible-binaries` was pretty complicated, and is now all gone, hence the net code reduction; * crucially, the `visible-binaries` is not going to work on Windows, because no symlinks on that platform and copying doesn't always work (due to limitations of GHC). With this PR merged in, porting to Windows should now be significantly easier. The downside is the following *breaking change*: we have lost the `extra_binaries` field in rules. But that field was introduced for unusual needs that inline-java has, and was a suspicious feature to begin with. Probably fixes #244. --- CHANGELOG.md | 1 + haskell/BUILD | 3 +- haskell/c2hs.bzl | 20 +- haskell/cc.bzl | 21 ++ haskell/doctest.bzl | 17 +- haskell/haddock.bzl | 36 ++-- haskell/lint.bzl | 18 +- haskell/private/actions/compile.bzl | 14 +- haskell/private/actions/link.bzl | 11 +- haskell/private/c2hs_wrapper.sh | 7 - haskell/private/context.bzl | 3 - ...dock_wrapper.sh => haddock_wrapper.sh.tpl} | 8 +- haskell/protobuf.bzl | 1 + haskell/toolchain.bzl | 199 ++---------------- 14 files changed, 126 insertions(+), 233 deletions(-) delete mode 100755 haskell/private/c2hs_wrapper.sh rename haskell/private/{haddock_wrapper.sh => haddock_wrapper.sh.tpl} (88%) diff --git a/CHANGELOG.md b/CHANGELOG.md index f2203d6cf3..7c00953dc3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/). * The `prebuilt_dependencies` attribute of all haskell rules had been deprecated two versions ago and is removed. Use `haskell_import` instead (see docs for usage). +* The `extra_binaries` field is now no longer supported. ## [0.7] - 2018-12-24 diff --git a/haskell/BUILD b/haskell/BUILD index a6b9f2c7e6..01e0bcea69 100644 --- a/haskell/BUILD +++ b/haskell/BUILD @@ -1,9 +1,8 @@ exports_files( glob(["*.bzl"]) + [ "assets/ghci_script", - "private/c2hs_wrapper.sh", "private/ghci_repl_wrapper.sh", - "private/haddock_wrapper.sh", + "private/haddock_wrapper.sh.tpl", ], ) diff --git a/haskell/c2hs.bzl b/haskell/c2hs.bzl index d7ed042900..ee977d53f8 100644 --- a/haskell/c2hs.bzl +++ b/haskell/c2hs.bzl @@ -29,7 +29,7 @@ def _c2hs_library_impl(ctx): args.add([chs_file.path, "-o", hs_file.path]) args.add(["-C-E"]) - args.add(["--cpp", hs.tools.cc.path]) + args.add(["--cpp", cc.tools.cc]) args.add("-C-includeghcplatform.h") args.add("-C-includeghcversion.h") args.add(["-C" + x for x in cc.cpp_flags]) @@ -48,14 +48,22 @@ def _c2hs_library_impl(ctx): ] args.add(chi_includes) - hs.actions.run( + hs.actions.run_shell( inputs = depset(transitive = [ depset(cc.hdrs), - depset([hs.tools.cc, hs.tools.ghc, hs.tools.c2hs, chs_file]), + depset([hs.tools.ghc, hs.tools.c2hs, chs_file]), depset(dep_chi_files), + depset(cc.files), ]), outputs = [hs_file, chi_file], - executable = ctx.file._chs_wrapper, + command = """ + # Include libdir in include path just like hsc2hs does. + libdir=$({ghc} --print-libdir) + {c2hs} -C-I$libdir/include "$@" + """.format( + ghc = hs.tools.ghc.path, + c2hs = hs.tools.c2hs.path, + ), mnemonic = "HaskellC2Hs", arguments = [args], env = hs.env, @@ -84,10 +92,6 @@ c2hs_library = rule( "src_strip_prefix": attr.string( doc = "Directory in which module hierarchy starts.", ), - "_chs_wrapper": attr.label( - allow_single_file = True, - default = Label("@io_tweag_rules_haskell//haskell:private/c2hs_wrapper.sh"), - ), "_cc_toolchain": attr.label( default = Label("@bazel_tools//tools/cpp:current_cc_toolchain"), ), diff --git a/haskell/cc.bzl b/haskell/cc.bzl index 1b55e3462f..c1a9c7f34c 100644 --- a/haskell/cc.bzl +++ b/haskell/cc.bzl @@ -22,6 +22,10 @@ load( CcInteropInfo = provider( doc = "Information needed for interop with cc rules.", fields = { + "tools": "Tools from the CC toolchain", + # See the following for why this is needed: + # https://stackoverflow.com/questions/52769846/custom-c-rule-with-the-cc-common-api + "files": "Files for all tools (input to any action that uses tools)", "hdrs": "CC headers", "cpp_flags": "Preprocessor flags", "compiler_flags": "Flags for compilation", @@ -121,7 +125,24 @@ def cc_interop_info(ctx): # XXX Workaround https://github.com/bazelbuild/bazel/issues/6876. linker_flags = [flag for flag in linker_flags if flag not in ["-shared"]] + tools = { + "ar": cc_toolchain.ar_executable(), + "cc": cc_toolchain.compiler_executable(), + "ld": cc_toolchain.ld_executable(), + "cpp": cc_toolchain.preprocessor_executable(), + } + + # 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. + if tools["ar"].find("libtool") >= 0: + tools["ar"] = "/usr/bin/ar" + return CcInteropInfo( + tools = struct(**tools), + files = ctx.files._cc_toolchain, hdrs = hdrs.to_list(), cpp_flags = cpp_flags, include_args = include_args, diff --git a/haskell/doctest.bzl b/haskell/doctest.bzl index 10bcf9f116..a0b006ff06 100644 --- a/haskell/doctest.bzl +++ b/haskell/doctest.bzl @@ -151,15 +151,26 @@ def _haskell_doctest_single(target, ctx): depset([exposed_modules_file]), depset( toolchain.doctest + - [hs.tools.cat, hs.tools.ghc], + [hs.tools.ghc], ), ]), outputs = [doctest_log], mnemonic = "HaskellDoctest", progress_message = "HaskellDoctest {}".format(ctx.label), command = """ - {doctest} "$@" $(cat {module_list} | tr , ' ') > {output} 2>&1 || rc=$? && cat {output} && exit $rc - """.format(doctest = toolchain.doctest[0].path, output = doctest_log.path, module_list = exposed_modules_file.path), + {env} + {doctest} "$@" $(cat {module_list} | tr , ' ') > {output} 2>&1 || rc=$? && cat {output} && exit $rc + """.format( + doctest = toolchain.doctest[0].path, + output = doctest_log.path, + module_list = exposed_modules_file.path, + # XXX Workaround + # https://github.com/bazelbuild/bazel/issues/5980. + env = "\n".join([ + "export {}={}".format(k, v) + for k, v in hs.env.items() + ]), + ), arguments = [args], # NOTE It looks like we must specify the paths here as well as via -L # flags because there are at least two different "consumers" of the info diff --git a/haskell/haddock.bzl b/haskell/haddock.bzl index c148ce4672..3088f8c4fc 100644 --- a/haskell/haddock.bzl +++ b/haskell/haddock.bzl @@ -89,6 +89,25 @@ def _haskell_doc_aspect_impl(target, ctx): depset([hs.toolchain.locale_archive]) if hs.toolchain.locale_archive != None else depset() ) + # TODO(mboes): we should be able to instantiate this template only + # once per toolchain instance, rather than here. + haddock_wrapper = ctx.actions.declare_file("haddock_wrapper") + ctx.actions.expand_template( + template = ctx.file._haddock_wrapper_tpl, + output = haddock_wrapper, + substitutions = { + "%{ghc-pkg}": hs.tools.ghc_pkg.path, + "%{haddock}": hs.tools.haddock.path, + # XXX Workaround + # https://github.com/bazelbuild/bazel/issues/5980. + "%{env}": "\n".join([ + "export {}={}".format(k, v) + for k, v in hs.env.items() + ]), + }, + is_executable = True, + ) + ctx.actions.run( inputs = depset(transitive = [ set.to_depset(target[HaskellBuildInfo].package_caches), @@ -105,24 +124,21 @@ def _haskell_doc_aspect_impl(target, ctx): set.to_depset(target[HaskellLibraryInfo].source_files), target[HaskellLibraryInfo].extra_source_files, depset([ - hs.tools.bash, hs.tools.ghc_pkg, hs.tools.haddock, - hs.tools.mktemp, - hs.tools.rmdir, ]), locale_archive_depset, ]), outputs = [haddock_file, html_dir], mnemonic = "HaskellHaddock", progress_message = "HaskellHaddock {}".format(ctx.label), - executable = ctx.file._haddock_wrapper, + executable = haddock_wrapper, arguments = [ prebuilt_deps, args, ghc_args, ], - env = hs.env, + use_default_shell_env = True, ) transitive_html.update({package_id: html_dir}) @@ -140,9 +156,9 @@ def _haskell_doc_aspect_impl(target, ctx): haskell_doc_aspect = aspect( _haskell_doc_aspect_impl, attrs = { - "_haddock_wrapper": attr.label( + "_haddock_wrapper_tpl": attr.label( allow_single_file = True, - default = Label("@io_tweag_rules_haskell//haskell:private/haddock_wrapper.sh"), + default = Label("@io_tweag_rules_haskell//haskell:private/haddock_wrapper.sh.tpl"), ), }, attr_aspects = ["deps"], @@ -191,11 +207,7 @@ def _haskell_doc_rule_impl(ctx): html_dict_copied[package_id] = output_dir ctx.actions.run_shell( - inputs = [ - hs.tools.cp, - hs.tools.mkdir, - html_dir, - ], + inputs = [html_dir], outputs = [output_dir], command = """ mkdir -p "{doc_dir}" diff --git a/haskell/lint.bzl b/haskell/lint.bzl index 7333f7a0a9..bebb9cca11 100644 --- a/haskell/lint.bzl +++ b/haskell/lint.bzl @@ -80,22 +80,26 @@ def _haskell_lint_aspect_impl(target, ctx): set.to_depset(build_info.interface_dirs), set.to_depset(build_info.dynamic_libraries), depset([e.mangled_lib for e in set.to_list(build_info.external_libraries)]), - depset([ - hs.tools.ghc, - hs.tools.cat, - ]), + depset([hs.tools.ghc]), ]), outputs = [lint_log], mnemonic = "HaskellLint", progress_message = "HaskellLint {}".format(ctx.label), command = """ - ghc "$@" > {output} 2>&1 || rc=$? && cat {output} && exit $rc - """.format( + {env} + {ghc} "$@" > {output} 2>&1 || rc=$? && cat {output} && exit $rc + """.format( ghc = hs.tools.ghc.path, output = lint_log.path, + # XXX Workaround + # https://github.com/bazelbuild/bazel/issues/5980. + env = "\n".join([ + "export {}={}".format(k, v) + for k, v in hs.env.items() + ]), ), arguments = [args], - env = hs.env, + use_default_shell_env = True, ) lint_info = HaskellLintInfo(outputs = set.singleton(lint_log)) diff --git a/haskell/private/actions/compile.bzl b/haskell/private/actions/compile.bzl index e778cdd7d1..c4e062ed39 100644 --- a/haskell/private/actions/compile.bzl +++ b/haskell/private/actions/compile.bzl @@ -36,8 +36,8 @@ def _process_hsc_file(hs, cc, hsc_flags, hsc_file): hs_out = declare_compiled(hs, hsc_file, ".hs", directory = hsc_dir_raw) args.add([hsc_file.path, "-o", hs_out.path]) - args.add(["-c", hs.tools.cc]) - args.add(["-l", hs.tools.cc]) + args.add(["-c", cc.tools.cc]) + args.add(["-l", cc.tools.cc]) args.add("-ighcplatform.h") args.add("-ighcversion.h") args.add(["--cflag=" + f for f in cc.cpp_flags]) @@ -49,7 +49,8 @@ def _process_hsc_file(hs, cc, hsc_flags, hsc_file): hs.actions.run( inputs = depset(transitive = [ depset(cc.hdrs), - depset([hs.tools.cc, hsc_file]), + depset([hsc_file]), + depset(cc.files), ]), outputs = [hs_out], mnemonic = "HaskellHsc2hs", @@ -250,7 +251,6 @@ def _compilation_defaults(hs, cc, java, dep_info, srcs, import_dir_map, extra_sr set.to_depset(dep_info.dynamic_libraries), depset([e.mangled_lib for e in set.to_list(dep_info.external_libraries)]), java.inputs, - depset([hs.tools.cc]), locale_archive_depset, ]), objects_dir = objects_dir, @@ -291,7 +291,8 @@ def compile_binary(hs, cc, java, dep_info, srcs, ls_modules, import_dir_map, ext hs.toolchain.actions.run_ghc( hs, - inputs = c.inputs + hs.extra_binaries, + cc, + inputs = c.inputs, outputs = c.outputs, mnemonic = "HaskellBuildBinary", progress_message = "HaskellBuildBinary {}".format(hs.label), @@ -348,7 +349,8 @@ def compile_library(hs, cc, java, dep_info, srcs, ls_modules, other_modules, exp hs.toolchain.actions.run_ghc( hs, - inputs = c.inputs + hs.extra_binaries, + cc, + inputs = c.inputs, outputs = c.outputs, mnemonic = "HaskellBuildLibrary", progress_message = "HaskellBuildLibrary {}".format(hs.label), diff --git a/haskell/private/actions/link.bzl b/haskell/private/actions/link.bzl index 58c6bc4ff2..a35c6621ff 100644 --- a/haskell/private/actions/link.bzl +++ b/haskell/private/actions/link.bzl @@ -205,6 +205,7 @@ def link_binary( ) hs.toolchain.actions.run_ghc( hs, + cc, inputs = depset(transitive = [ depset(extra_srcs), set.to_depset(dep_info.package_caches), @@ -213,7 +214,6 @@ def link_binary( depset(dep_info.static_libraries_prof), depset([objects_dir]), depset([e.mangled_lib for e in set.to_list(dep_info.external_libraries)]), - depset(hs.extra_binaries), ]), outputs = [compile_output], mnemonic = "HaskellLinkBinary", @@ -298,8 +298,7 @@ def link_library_static(hs, cc, dep_info, objects_dir, my_pkg_id, with_profiling with_profiling = with_profiling, ) args = hs.actions.args() - inputs = ([objects_dir, objects_dir_manifest, hs.tools.ar] + - hs.tools_runfiles.ar + hs.extra_binaries) + inputs = [objects_dir, objects_dir_manifest] + cc.files if hs.toolchain.is_darwin: # On Darwin, ar doesn't support params files. @@ -316,7 +315,7 @@ def link_library_static(hs, cc, dep_info, objects_dir, my_pkg_id, with_profiling inputs = inputs, outputs = [static_library], mnemonic = "HaskellLinkStaticLibrary", - command = "{ar} qc $1 $(< $2)".format(ar = hs.tools.ar.path), + command = "{ar} qc $1 $(< $2)".format(ar = cc.tools.ar), arguments = [args], # Use the default macosx toolchain @@ -332,7 +331,7 @@ def link_library_static(hs, cc, dep_info, objects_dir, my_pkg_id, with_profiling inputs = inputs, outputs = [static_library], mnemonic = "HaskellLinkStaticLibrary", - executable = hs.tools.ar, + executable = cc.tools.ar, arguments = [args], ) @@ -405,8 +404,8 @@ def link_library_dynamic(hs, cc, dep_info, extra_srcs, objects_dir, my_pkg_id): hs.toolchain.actions.run_ghc( hs, + cc, inputs = depset([objects_dir], transitive = [ - depset(hs.extra_binaries), depset(extra_srcs), set.to_depset(dep_info.package_caches), set.to_depset(dep_info.dynamic_libraries), diff --git a/haskell/private/c2hs_wrapper.sh b/haskell/private/c2hs_wrapper.sh deleted file mode 100755 index a4a5b20658..0000000000 --- a/haskell/private/c2hs_wrapper.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/sh - -set -e - -# Include libdir in include path just like hsc2hs does. -libdir=$(ghc --print-libdir) -c2hs -C-I$libdir/include ${1+"$@"} diff --git a/haskell/private/context.bzl b/haskell/private/context.bzl index 21be3437c5..7cb465ffe3 100644 --- a/haskell/private/context.bzl +++ b/haskell/private/context.bzl @@ -22,7 +22,6 @@ def haskell_context(ctx, attr = None): ) env = { - "PATH": toolchain.visible_bin_path, "LANG": toolchain.locale, } @@ -35,8 +34,6 @@ def haskell_context(ctx, attr = None): label = ctx.label, toolchain = toolchain, tools = toolchain.tools, - tools_runfiles = toolchain.tools_runfiles, - extra_binaries = toolchain.extra_binaries, src_root = src_root, env = env, mode = ctx.var["COMPILATION_MODE"], diff --git a/haskell/private/haddock_wrapper.sh b/haskell/private/haddock_wrapper.sh.tpl similarity index 88% rename from haskell/private/haddock_wrapper.sh rename to haskell/private/haddock_wrapper.sh.tpl index ad98af8a1e..c359da1c32 100755 --- a/haskell/private/haddock_wrapper.sh +++ b/haskell/private/haddock_wrapper.sh.tpl @@ -4,6 +4,8 @@ set -eo pipefail +%{env} + PREBUILT_DEPS_FILE=$1 shift @@ -19,8 +21,8 @@ do # If there were more than one file, going by the output for the `depends`, # the file names would be separated by a space character. # https://downloads.haskell.org/~ghc/latest/docs/html/users_guide/packages.html#installedpackageinfo-a-package-specification - haddock_interfaces=$(ghc-pkg --simple-output field $pkg haddock-interfaces) - haddock_html=$(ghc-pkg --simple-output field $pkg haddock-html) + haddock_interfaces=$(%{ghc-pkg} --simple-output field $pkg haddock-interfaces) + haddock_html=$(%{ghc-pkg} --simple-output field $pkg haddock-html) # Sometimes the referenced `.haddock` file does not exist # (e.g. for `nixpkgs.haskellPackages` deps with haddock disabled). @@ -43,5 +45,5 @@ cleanup() { rmdir "$TEMP"; } # XXX Override TMPDIR to prevent race conditions on certain platforms. # This is a workaround for # https://github.com/haskell/haddock/issues/894. -TMPDIR=$TEMP haddock "${extra_args[@]}" "$@" +TMPDIR=$TEMP %{haddock} "${extra_args[@]}" "$@" cleanup diff --git a/haskell/protobuf.bzl b/haskell/protobuf.bzl index a92d0c5191..409613dd1e 100644 --- a/haskell/protobuf.bzl +++ b/haskell/protobuf.bzl @@ -161,6 +161,7 @@ def _haskell_proto_aspect_impl(target, ctx): files = struct( srcs = hs_files, extra_srcs = depset(), + _cc_toolchain = ctx.files._cc_toolchain, ), executable = struct( _ls_modules = ctx.executable._ls_modules, diff --git a/haskell/toolchain.bzl b/haskell/toolchain.bzl index 1c8dac16a0..ad45bbe42e 100644 --- a/haskell/toolchain.bzl +++ b/haskell/toolchain.bzl @@ -1,7 +1,6 @@ """Rules for defining toolchains""" load("@bazel_skylib//lib:paths.bzl", "paths") -load("@bazel_tools//tools/cpp:toolchain_utils.bzl", "find_cpp_toolchain") load( ":private/actions/compile.bzl", "compile_binary", @@ -18,7 +17,7 @@ load(":private/set.bzl", "set") _GHC_BINARIES = ["ghc", "ghc-pkg", "hsc2hs", "haddock", "ghci"] -def _run_ghc(hs, inputs, outputs, mnemonic, arguments, params_file = None, env = None, progress_message = None): +def _run_ghc(hs, cc, inputs, outputs, mnemonic, arguments, params_file = None, env = None, progress_message = None): if not env: env = hs.env @@ -27,13 +26,13 @@ def _run_ghc(hs, inputs, outputs, mnemonic, arguments, params_file = None, env = args.add([ # GHC uses C compiler for assemly, linking and preprocessing as well. "-pgma", - hs.tools.cc.path, + cc.tools.cc, "-pgmc", - hs.tools.cc.path, + cc.tools.cc, "-pgml", - hs.tools.cc.path, + cc.tools.cc, "-pgmP", - hs.tools.cc.path, + cc.tools.cc, # Setting -pgm* flags explicitly has the unfortunate side effect # of resetting any program flags in the GHC settings file. So we # restore them here. See @@ -46,11 +45,10 @@ def _run_ghc(hs, inputs, outputs, mnemonic, arguments, params_file = None, env = extra_inputs = [ hs.tools.ghc, - hs.tools.cc, # Depend on the version file of the Haskell toolchain, # to ensure the version comparison check is run first. hs.toolchain.version_file, - ] + ] + cc.files if params_file: command = '${1+"$@"} $(< %s)' % params_file.path extra_inputs.append(params_file) @@ -75,9 +73,6 @@ def _run_ghc(hs, inputs, outputs, mnemonic, arguments, params_file = None, env = return args def _haskell_toolchain_impl(ctx): - cc_toolchain = find_cpp_toolchain(ctx) - - # Check that we have all that we want. for tool in _GHC_BINARIES: if tool not in [t.basename for t in ctx.files.tools]: fail("Cannot find {} in {}".format(tool, ctx.attr.tools.label)) @@ -86,38 +81,33 @@ def _haskell_toolchain_impl(ctx): ghc_binaries = {} for tool in ctx.files.tools: if tool.basename in _GHC_BINARIES: - ghc_binaries[tool.basename] = tool.path + ghc_binaries[tool.basename] = tool # Run a version check on the compiler. - compiler = None - for t in ctx.files.tools: - if t.basename == "ghc": - if compiler: - fail("There can only be one tool named `ghc` in scope") - compiler = t version_file = ctx.actions.declare_file("ghc-version") + ghc = ghc_binaries["ghc"] ctx.actions.run_shell( - inputs = [compiler], + inputs = [ghc], outputs = [version_file], mnemonic = "HaskellVersionCheck", command = """ - {compiler} --numeric-version > {version_file} - if [[ "{expected_version}" != "$(< {version_file})" ]] - then - echo ERROR: GHC version does not match expected version. Your haskell_toolchain specifies {expected_version}, but you have $(< {version_file}) in your environment. - exit 1 - fi - """.format( - compiler = compiler.path, +{ghc} --numeric-version > {version_file} +if [[ "{expected_version}" != "$(< {version_file})" ]] +then + echo ERROR: GHC version does not match expected version. + echo Your haskell_toolchain specifies {expected_version}, + echo but you have $(< {version_file}) in your environment. +exit 1 +fi + """.format( + ghc = ghc.path, version_file = version_file.path, expected_version = ctx.attr.version, ), ) # Get the versions of every prebuilt package. - for t in ctx.files.tools: - if t.basename == "ghc-pkg": - ghc_pkg = t + ghc_pkg = ghc_binaries["ghc-pkg"] pkgdb_file = ctx.actions.declare_file("ghc-global-pkgdb") ctx.actions.run_shell( inputs = [ghc_pkg], @@ -129,138 +119,13 @@ def _haskell_toolchain_impl(ctx): ), ) - # NOTE The only way to let various executables know where other - # executables are located is often only via the PATH environment variable. - # For example, ghc uses gcc which is found on PATH. This forces us provide - # correct PATH value (with e.g. gcc on it) when we call executables such - # as ghc. However, this hurts hermeticity because if we construct PATH - # naively, i.e. through concatenation of directory names of all - # executables of interest, we also make other neighboring executables - # visible, and they indeed can influence the building process, which is - # undesirable. - # - # To prevent this, we create a special directory populated with symbolic - # links to all executables we want to make visible, and only to them. Then - # we make all rules depend on some of these symlinks, and provide PATH - # (always the same) that points only to this prepared directory with - # symlinks. This helps hermeticity and forces us to be explicit about all - # programs we need/use for building. Setting PATH and listing as inputs - # are both necessary for a tool to be available with this approach. - - visible_binaries = "visible-binaries" - symlinks = set.empty() - inputs = [] - inputs.extend(ctx.files.tools) - inputs.extend(ctx.files._crosstool) - - targets_r = {} - targets_r.update({ - "ar": cc_toolchain.ar_executable(), - "cc": cc_toolchain.compiler_executable(), - "ld": cc_toolchain.ld_executable(), - "nm": cc_toolchain.nm_executable(), - "cpp": cc_toolchain.preprocessor_executable(), - "strip": cc_toolchain.strip_executable(), - }) - targets_r.update(ghc_binaries) - - # 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. - if targets_r["ar"].find("libtool") >= 0: - targets_r["ar"] = "/usr/bin/ar" - - ar_runfiles = [] - - # "xcrunwrapper.sh" is a Bazel-generated dependency of the `ar` program on macOS. - xcrun_candidates = [f for f in ctx.files._crosstool if paths.basename(f.path) == "xcrunwrapper.sh"] - if xcrun_candidates: - xcrun = xcrun_candidates[0] - ar_runfiles += [xcrun] - targets_r["xcrunwrapper.sh"] = xcrun.path - if ctx.attr.c2hs != None: - targets_r["c2hs"] = ctx.file.c2hs.path - inputs.append(ctx.file.c2hs) - - extra_binaries_names = set.empty() - for binary in ctx.files.extra_binaries: - targets_r[binary.basename] = binary.path - inputs.append(binary) - set.mutable_insert(extra_binaries_names, binary.basename) - - extra_binaries_files = [] - for target in targets_r: - symlink = ctx.actions.declare_file( - paths.join(visible_binaries, target), - ) - symlink_target = targets_r[target] - if not paths.is_absolute(symlink_target): - symlink_target = paths.join("/".join([".."] * len(symlink.dirname.split("/"))), symlink_target) - ctx.actions.run( - inputs = inputs, - outputs = [symlink], - mnemonic = "Symlink", - executable = "ln", - # FIXME Currently this part of the process is not hermetic. This - # should be adjusted when - # - # https://github.com/bazelbuild/bazel/issues/4681 - # - # is implemented. - use_default_shell_env = True, - arguments = [ - "-s", - symlink_target, - symlink.path, - ], - ) - if target == "xcrunwrapper.sh": - ar_runfiles += [symlink] - - if set.is_member(extra_binaries_names, target): - extra_binaries_files += [symlink] - - set.mutable_insert(symlinks, symlink) - - targets_w = [ - "bash", - "cat", - "tr", - "cp", - "grep", - "ln", - "mkdir", - "mktemp", - "rmdir", - ] - - for target in targets_w: - symlink = ctx.actions.declare_file( - paths.join(visible_binaries, paths.basename(target)), - ) - ctx.actions.run_shell( - inputs = ctx.files.tools, - outputs = [symlink], - mnemonic = "Symlink", - command = """ - mkdir -p $(dirname "{symlink}") - ln -s $(which "{target}") "{symlink}" - """.format( - target = target, - symlink = symlink.path, - ), - use_default_shell_env = True, - ) - set.mutable_insert(symlinks, symlink) + ghc_binaries["c2hs"] = ctx.file.c2hs tools_struct_args = { - tool.basename.replace("-", "_"): tool - for tool in set.to_list(symlinks) + name.replace("-", "_"): file + for name, file in ghc_binaries.items() } - tools_runfiles_struct_args = {"ar": ar_runfiles} locale_archive = None @@ -271,8 +136,6 @@ def _haskell_toolchain_impl(ctx): platform_common.ToolchainInfo( name = ctx.label.name, tools = struct(**tools_struct_args), - tools_runfiles = struct(**tools_runfiles_struct_args), - extra_binaries = extra_binaries_files, compiler_flags = ctx.attr.compiler_flags, repl_ghci_args = ctx.attr.repl_ghci_args, haddock_flags = ctx.attr.haddock_flags, @@ -288,11 +151,6 @@ def _haskell_toolchain_impl(ctx): package = package, run_ghc = _run_ghc, ), - # All symlinks are guaranteed to be in the same directory so we just - # provide directory name of the first one (the collection cannot be - # empty). The rest of the program may rely consider visible_bin_path - # as the path to visible binaries, without recalculations. - visible_bin_path = set.to_list(symlinks)[0].dirname, is_darwin = ctx.attr.is_darwin, version = ctx.attr.version, # Pass through the version_file, that it can be required as @@ -331,17 +189,6 @@ _haskell_toolchain = rule( doc = "Whether compile on and for Darwin (macOS).", mandatory = True, ), - # TODO: document - "_crosstool": attr.label( - default = Label("//tools/defaults:crosstool"), - ), - "_cc_toolchain": attr.label( - default = Label("@bazel_tools//tools/cpp:current_cc_toolchain"), - ), - "extra_binaries": attr.label_list( - doc = "Additional binaries necessary for building", - default = [], - ), "locale": attr.string( default = "en_US.UTF-8", doc = "Locale that will be set during compiler invocations.",