feat(third_party/bazel): Check in rules_haskell from Tweag
This commit is contained in:
parent
2eb1dc26e4
commit
f723b8b878
479 changed files with 51484 additions and 0 deletions
563
third_party/bazel/rules_haskell/haskell/private/actions/compile.bzl
vendored
Normal file
563
third_party/bazel/rules_haskell/haskell/private/actions/compile.bzl
vendored
Normal file
|
|
@ -0,0 +1,563 @@
|
|||
"""Actions for compiling Haskell source code"""
|
||||
|
||||
load(":private/packages.bzl", "expose_packages", "pkg_info_to_compile_flags")
|
||||
load("@bazel_skylib//lib:dicts.bzl", "dicts")
|
||||
load("@bazel_skylib//lib:paths.bzl", "paths")
|
||||
load(
|
||||
":private/path_utils.bzl",
|
||||
"declare_compiled",
|
||||
"module_name",
|
||||
"target_unique_name",
|
||||
)
|
||||
load(":private/pkg_id.bzl", "pkg_id")
|
||||
load(":private/version_macros.bzl", "version_macro_includes")
|
||||
load(
|
||||
":providers.bzl",
|
||||
"GhcPluginInfo",
|
||||
"get_libs_for_ghc_linker",
|
||||
"merge_HaskellCcInfo",
|
||||
)
|
||||
load(":private/set.bzl", "set")
|
||||
|
||||
def _process_hsc_file(hs, cc, hsc_flags, hsc_inputs, hsc_file):
|
||||
"""Process a single hsc file.
|
||||
|
||||
Args:
|
||||
hs: Haskell context.
|
||||
cc: CcInteropInfo, information about C dependencies.
|
||||
hsc_flags: extra flags to pass to hsc2hs
|
||||
hsc_inputs: extra file inputs for the hsc2hs command
|
||||
hsc_file: hsc file to process.
|
||||
|
||||
Returns:
|
||||
(File, string): Haskell source file created by processing hsc_file and
|
||||
new import directory containing the produced file.
|
||||
"""
|
||||
args = hs.actions.args()
|
||||
|
||||
# Output a Haskell source file.
|
||||
hsc_dir_raw = paths.join("_hsc", hs.name)
|
||||
hs_out = declare_compiled(hs, hsc_file, ".hs", directory = hsc_dir_raw)
|
||||
args.add_all([hsc_file.path, "-o", hs_out.path])
|
||||
|
||||
args.add_all(["-c", cc.tools.cc])
|
||||
args.add_all(["-l", cc.tools.cc])
|
||||
args.add("-ighcplatform.h")
|
||||
args.add("-ighcversion.h")
|
||||
args.add_all(["--cflag=" + f for f in cc.cpp_flags])
|
||||
args.add_all(["--cflag=" + f for f in cc.compiler_flags])
|
||||
args.add_all(["--cflag=" + f for f in cc.include_args])
|
||||
args.add_all(["--lflag=" + f for f in cc.linker_flags])
|
||||
args.add_all(hsc_flags)
|
||||
|
||||
# Add an empty PATH variable if not already specified in hs.env.
|
||||
# Needed to avoid a "Couldn't read PATH" error on Windows.
|
||||
#
|
||||
# On Unix platforms, though, we musn't set PATH as it is automatically set up
|
||||
# by the run action, unless already set in the env parameter. This triggers
|
||||
# build errors when using GHC bindists on Linux.
|
||||
if hs.env.get("PATH") == None and hs.toolchain.is_windows:
|
||||
hs.env["PATH"] = ""
|
||||
|
||||
hs.actions.run(
|
||||
inputs = depset(transitive = [
|
||||
depset(cc.hdrs),
|
||||
depset([hsc_file]),
|
||||
depset(cc.files),
|
||||
depset(hsc_inputs),
|
||||
]),
|
||||
outputs = [hs_out],
|
||||
mnemonic = "HaskellHsc2hs",
|
||||
executable = hs.tools.hsc2hs,
|
||||
arguments = [args],
|
||||
env = hs.env,
|
||||
)
|
||||
|
||||
idir = paths.join(
|
||||
hs.bin_dir.path,
|
||||
hs.label.package,
|
||||
hsc_dir_raw,
|
||||
)
|
||||
|
||||
return hs_out, idir
|
||||
|
||||
def _compilation_defaults(hs, cc, java, dep_info, plugin_dep_info, srcs, import_dir_map, extra_srcs, user_compile_flags, with_profiling, my_pkg_id, version, plugins):
|
||||
"""Compute variables common to all compilation targets (binary and library).
|
||||
|
||||
Returns:
|
||||
struct with the following fields:
|
||||
args: default argument list
|
||||
compile_flags: arguments that were used to compile the package
|
||||
inputs: default inputs
|
||||
input_manifests: input manifests
|
||||
outputs: default outputs
|
||||
objects_dir: object files directory
|
||||
interfaces_dir: interface files directory
|
||||
source_files: set of files that contain Haskell modules
|
||||
extra_source_files: depset of non-Haskell source files
|
||||
import_dirs: c2hs Import hierarchy roots
|
||||
env: default environment variables
|
||||
"""
|
||||
|
||||
compile_flags = []
|
||||
|
||||
# GHC expects the CC compiler as the assembler, but segregates the
|
||||
# set of flags to pass to it when used as an assembler. So we have
|
||||
# to set both -optc and -opta.
|
||||
cc_args = [
|
||||
"-optc" + f
|
||||
for f in cc.compiler_flags
|
||||
] + [
|
||||
"-opta" + f
|
||||
for f in cc.compiler_flags
|
||||
]
|
||||
compile_flags += cc_args
|
||||
|
||||
interface_dir_raw = "_iface_prof" if with_profiling else "_iface"
|
||||
object_dir_raw = "_obj_prof" if with_profiling else "_obj"
|
||||
|
||||
# Declare file directories.
|
||||
#
|
||||
# NOTE: We could have used -outputdir here and a single output
|
||||
# directory. But keeping interface and object files separate has
|
||||
# one advantage: if interface files are invariant under
|
||||
# a particular code change, then we don't need to rebuild
|
||||
# downstream.
|
||||
if my_pkg_id:
|
||||
# If we're compiling a package, put the interfaces inside the
|
||||
# package directory.
|
||||
interfaces_dir = hs.actions.declare_directory(
|
||||
paths.join(
|
||||
pkg_id.to_string(my_pkg_id),
|
||||
interface_dir_raw,
|
||||
),
|
||||
)
|
||||
else:
|
||||
interfaces_dir = hs.actions.declare_directory(
|
||||
paths.join(interface_dir_raw, hs.name),
|
||||
)
|
||||
objects_dir = hs.actions.declare_directory(
|
||||
paths.join(object_dir_raw, hs.name),
|
||||
)
|
||||
|
||||
# Default compiler flags.
|
||||
compile_flags += hs.toolchain.compiler_flags
|
||||
compile_flags += user_compile_flags
|
||||
|
||||
# Work around macOS linker limits. This fix has landed in GHC HEAD, but is
|
||||
# not yet in a release; plus, we still want to support older versions of
|
||||
# GHC. For details, see: https://phabricator.haskell.org/D4714
|
||||
if hs.toolchain.is_darwin:
|
||||
compile_flags += ["-optl-Wl,-dead_strip_dylibs"]
|
||||
|
||||
compile_flags.extend(
|
||||
pkg_info_to_compile_flags(
|
||||
expose_packages(
|
||||
dep_info,
|
||||
lib_info = None,
|
||||
use_direct = True,
|
||||
use_my_pkg_id = my_pkg_id,
|
||||
custom_package_databases = None,
|
||||
version = version,
|
||||
),
|
||||
),
|
||||
)
|
||||
compile_flags.extend(
|
||||
pkg_info_to_compile_flags(
|
||||
expose_packages(
|
||||
plugin_dep_info,
|
||||
lib_info = None,
|
||||
use_direct = True,
|
||||
use_my_pkg_id = my_pkg_id,
|
||||
custom_package_databases = None,
|
||||
version = version,
|
||||
),
|
||||
for_plugin = True,
|
||||
),
|
||||
)
|
||||
|
||||
header_files = []
|
||||
boot_files = []
|
||||
source_files = set.empty()
|
||||
|
||||
# Forward all "-D" and "-optP-D" flags to hsc2hs
|
||||
hsc_flags = []
|
||||
hsc_flags += ["--cflag=" + x for x in user_compile_flags if x.startswith("-D")]
|
||||
hsc_flags += ["--cflag=" + x[len("-optP"):] for x in user_compile_flags if x.startswith("-optP-D")]
|
||||
|
||||
hsc_inputs = []
|
||||
if version:
|
||||
(version_macro_headers, version_macro_flags) = version_macro_includes(dep_info)
|
||||
hsc_flags += ["--cflag=" + x for x in version_macro_flags]
|
||||
hsc_inputs += set.to_list(version_macro_headers)
|
||||
|
||||
# Add import hierarchy root.
|
||||
# Note that this is not perfect, since GHC requires hs-boot files
|
||||
# to be in the same directory as the corresponding .hs file. Thus
|
||||
# the two must both have the same root; i.e., both plain files,
|
||||
# both in bin_dir, or both in genfiles_dir.
|
||||
|
||||
import_dirs = set.from_list([
|
||||
hs.src_root,
|
||||
paths.join(hs.bin_dir.path, hs.src_root),
|
||||
paths.join(hs.genfiles_dir.path, hs.src_root),
|
||||
])
|
||||
|
||||
for s in srcs:
|
||||
if s.extension == "h":
|
||||
header_files.append(s)
|
||||
elif s.extension == "hsc":
|
||||
s0, idir = _process_hsc_file(hs, cc, hsc_flags, hsc_inputs, s)
|
||||
set.mutable_insert(source_files, s0)
|
||||
set.mutable_insert(import_dirs, idir)
|
||||
elif s.extension in ["hs-boot", "lhs-boot"]:
|
||||
boot_files.append(s)
|
||||
else:
|
||||
set.mutable_insert(source_files, s)
|
||||
|
||||
if s in import_dir_map:
|
||||
idir = import_dir_map[s]
|
||||
set.mutable_insert(import_dirs, idir)
|
||||
|
||||
compile_flags += ["-i{0}".format(d) for d in set.to_list(import_dirs)]
|
||||
|
||||
# Write the -optP flags to a parameter file because they can be very long on Windows
|
||||
# e.g. 27Kb for grpc-haskell
|
||||
# Equivalent to: compile_flags += ["-optP" + f for f in cc.cpp_flags]
|
||||
optp_args_file = hs.actions.declare_file("optp_args_%s" % hs.name)
|
||||
optp_args = hs.actions.args()
|
||||
optp_args.add_all(cc.cpp_flags)
|
||||
optp_args.set_param_file_format("multiline")
|
||||
hs.actions.write(optp_args_file, optp_args)
|
||||
compile_flags += ["-optP@" + optp_args_file.path]
|
||||
|
||||
compile_flags += cc.include_args
|
||||
|
||||
locale_archive_depset = (
|
||||
depset([hs.toolchain.locale_archive]) if hs.toolchain.locale_archive != None else depset()
|
||||
)
|
||||
|
||||
# This is absolutely required otherwise GHC doesn't know what package it's
|
||||
# creating `Name`s for to put them in Haddock interface files which then
|
||||
# results in Haddock not being able to find names for linking in
|
||||
# environment after reading its interface file later.
|
||||
if my_pkg_id != None:
|
||||
unit_id_args = [
|
||||
"-this-unit-id",
|
||||
pkg_id.to_string(my_pkg_id),
|
||||
"-optP-DCURRENT_PACKAGE_KEY=\"{}\"".format(pkg_id.to_string(my_pkg_id)),
|
||||
]
|
||||
compile_flags += unit_id_args
|
||||
|
||||
args = hs.actions.args()
|
||||
|
||||
# Compilation mode. Allow rule-supplied compiler flags to override it.
|
||||
if hs.mode == "opt":
|
||||
args.add("-O2")
|
||||
|
||||
args.add("-static")
|
||||
if with_profiling:
|
||||
args.add("-prof", "-fexternal-interpreter")
|
||||
|
||||
# Common flags
|
||||
args.add_all([
|
||||
"-v0",
|
||||
"-no-link",
|
||||
"-fPIC",
|
||||
"-hide-all-packages",
|
||||
# Should never trigger in sandboxed builds, but can be useful
|
||||
# to debug issues in non-sandboxed builds.
|
||||
"-Wmissing-home-modules",
|
||||
])
|
||||
|
||||
# Output directories
|
||||
args.add_all([
|
||||
"-odir",
|
||||
objects_dir.path,
|
||||
"-hidir",
|
||||
interfaces_dir.path,
|
||||
])
|
||||
|
||||
# Interface files with profiling have to have the extension "p_hi":
|
||||
# https://downloads.haskell.org/~ghc/latest/docs/html/users_guide/packages.html#installedpackageinfo-a-package-specification
|
||||
# otherwise we won't be able to register them with ghc-pkg.
|
||||
if with_profiling:
|
||||
args.add_all([
|
||||
"-hisuf",
|
||||
"p_hi",
|
||||
"-osuf",
|
||||
"p_o",
|
||||
])
|
||||
|
||||
args.add_all(compile_flags)
|
||||
|
||||
# Plugins
|
||||
for plugin in plugins:
|
||||
args.add("-fplugin={}".format(plugin[GhcPluginInfo].module))
|
||||
for opt in plugin[GhcPluginInfo].args:
|
||||
args.add_all(["-fplugin-opt", "{}:{}".format(plugin[GhcPluginInfo].module, opt)])
|
||||
|
||||
plugin_tool_inputs = [plugin[GhcPluginInfo].tool_inputs for plugin in plugins]
|
||||
plugin_tool_input_manifests = [
|
||||
manifest
|
||||
for plugin in plugins
|
||||
for manifest in plugin[GhcPluginInfo].tool_input_manifests
|
||||
]
|
||||
|
||||
# Pass source files
|
||||
for f in set.to_list(source_files):
|
||||
args.add(f)
|
||||
|
||||
extra_source_files = depset(
|
||||
transitive = [extra_srcs, depset(header_files), depset(boot_files)],
|
||||
)
|
||||
|
||||
# Transitive library dependencies for runtime.
|
||||
(library_deps, ld_library_deps, ghc_env) = get_libs_for_ghc_linker(
|
||||
hs,
|
||||
merge_HaskellCcInfo(
|
||||
dep_info.transitive_cc_dependencies,
|
||||
plugin_dep_info.transitive_cc_dependencies,
|
||||
),
|
||||
)
|
||||
|
||||
return struct(
|
||||
args = args,
|
||||
compile_flags = compile_flags,
|
||||
inputs = depset(transitive = [
|
||||
depset(header_files),
|
||||
depset(boot_files),
|
||||
set.to_depset(source_files),
|
||||
extra_source_files,
|
||||
depset(cc.hdrs),
|
||||
set.to_depset(dep_info.package_databases),
|
||||
set.to_depset(dep_info.interface_dirs),
|
||||
depset(dep_info.static_libraries),
|
||||
depset(dep_info.static_libraries_prof),
|
||||
set.to_depset(dep_info.dynamic_libraries),
|
||||
set.to_depset(plugin_dep_info.package_databases),
|
||||
set.to_depset(plugin_dep_info.interface_dirs),
|
||||
depset(plugin_dep_info.static_libraries),
|
||||
depset(plugin_dep_info.static_libraries_prof),
|
||||
set.to_depset(plugin_dep_info.dynamic_libraries),
|
||||
depset(library_deps),
|
||||
depset(ld_library_deps),
|
||||
java.inputs,
|
||||
locale_archive_depset,
|
||||
depset(transitive = plugin_tool_inputs),
|
||||
depset([optp_args_file]),
|
||||
]),
|
||||
input_manifests = plugin_tool_input_manifests,
|
||||
objects_dir = objects_dir,
|
||||
interfaces_dir = interfaces_dir,
|
||||
outputs = [objects_dir, interfaces_dir],
|
||||
source_files = source_files,
|
||||
extra_source_files = depset(transitive = [extra_source_files, depset([optp_args_file])]),
|
||||
import_dirs = import_dirs,
|
||||
env = dicts.add(
|
||||
ghc_env,
|
||||
java.env,
|
||||
hs.env,
|
||||
),
|
||||
)
|
||||
|
||||
def _hpc_compiler_args(hs):
|
||||
hpcdir = "{}/{}/.hpc".format(hs.bin_dir.path, hs.package_root)
|
||||
return ["-fhpc", "-hpcdir", hpcdir]
|
||||
|
||||
def _coverage_datum(mix_file, src_file, target_label):
|
||||
return struct(
|
||||
mix_file = mix_file,
|
||||
src_file = src_file,
|
||||
target_label = target_label,
|
||||
)
|
||||
|
||||
def compile_binary(
|
||||
hs,
|
||||
cc,
|
||||
java,
|
||||
dep_info,
|
||||
plugin_dep_info,
|
||||
srcs,
|
||||
ls_modules,
|
||||
import_dir_map,
|
||||
extra_srcs,
|
||||
user_compile_flags,
|
||||
dynamic,
|
||||
with_profiling,
|
||||
main_function,
|
||||
version,
|
||||
inspect_coverage = False,
|
||||
plugins = []):
|
||||
"""Compile a Haskell target into object files suitable for linking.
|
||||
|
||||
Returns:
|
||||
struct with the following fields:
|
||||
object_files: list of static object files
|
||||
object_dyn_files: list of dynamic object files
|
||||
modules: set of module names
|
||||
source_files: set of Haskell source files
|
||||
"""
|
||||
c = _compilation_defaults(hs, cc, java, dep_info, plugin_dep_info, srcs, import_dir_map, extra_srcs, user_compile_flags, with_profiling, my_pkg_id = None, version = version, plugins = plugins)
|
||||
c.args.add_all(["-main-is", main_function])
|
||||
if dynamic:
|
||||
# For binaries, GHC creates .o files even for code to be
|
||||
# linked dynamically. So we have to force the object suffix to
|
||||
# be consistent with the dynamic object suffix in the library
|
||||
# case.
|
||||
c.args.add_all(["-dynamic", "-osuf dyn_o"])
|
||||
|
||||
coverage_data = []
|
||||
if inspect_coverage:
|
||||
c.args.add_all(_hpc_compiler_args(hs))
|
||||
for src_file in srcs:
|
||||
module = module_name(hs, src_file)
|
||||
mix_file = hs.actions.declare_file(".hpc/{module}.mix".format(module = module))
|
||||
coverage_data.append(_coverage_datum(mix_file, src_file, hs.label))
|
||||
|
||||
hs.toolchain.actions.run_ghc(
|
||||
hs,
|
||||
cc,
|
||||
inputs = c.inputs,
|
||||
input_manifests = c.input_manifests,
|
||||
outputs = c.outputs + [datum.mix_file for datum in coverage_data],
|
||||
mnemonic = "HaskellBuildBinary" + ("Prof" if with_profiling else ""),
|
||||
progress_message = "HaskellBuildBinary {}".format(hs.label),
|
||||
env = c.env,
|
||||
arguments = c.args,
|
||||
)
|
||||
|
||||
if with_profiling:
|
||||
exposed_modules_file = None
|
||||
else:
|
||||
exposed_modules_file = hs.actions.declare_file(
|
||||
target_unique_name(hs, "exposed-modules"),
|
||||
)
|
||||
hs.actions.run(
|
||||
inputs = [c.interfaces_dir, hs.toolchain.global_pkg_db],
|
||||
outputs = [exposed_modules_file],
|
||||
executable = ls_modules,
|
||||
arguments = [
|
||||
c.interfaces_dir.path,
|
||||
hs.toolchain.global_pkg_db.path,
|
||||
"/dev/null", # no hidden modules
|
||||
"/dev/null", # no reexported modules
|
||||
exposed_modules_file.path,
|
||||
],
|
||||
use_default_shell_env = True,
|
||||
)
|
||||
|
||||
return struct(
|
||||
objects_dir = c.objects_dir,
|
||||
source_files = c.source_files,
|
||||
extra_source_files = c.extra_source_files,
|
||||
import_dirs = c.import_dirs,
|
||||
compile_flags = c.compile_flags,
|
||||
exposed_modules_file = exposed_modules_file,
|
||||
coverage_data = coverage_data,
|
||||
)
|
||||
|
||||
def compile_library(
|
||||
hs,
|
||||
cc,
|
||||
java,
|
||||
dep_info,
|
||||
plugin_dep_info,
|
||||
srcs,
|
||||
ls_modules,
|
||||
other_modules,
|
||||
exposed_modules_reexports,
|
||||
import_dir_map,
|
||||
extra_srcs,
|
||||
user_compile_flags,
|
||||
with_shared,
|
||||
with_profiling,
|
||||
my_pkg_id,
|
||||
plugins = []):
|
||||
"""Build arguments for Haskell package build.
|
||||
|
||||
Returns:
|
||||
struct with the following fields:
|
||||
interfaces_dir: directory containing interface files
|
||||
interface_files: list of interface files
|
||||
object_files: list of static object files
|
||||
object_dyn_files: list of dynamic object files
|
||||
compile_flags: list of string arguments suitable for Haddock
|
||||
modules: set of module names
|
||||
source_files: set of Haskell module files
|
||||
import_dirs: import directories that should make all modules visible (for GHCi)
|
||||
"""
|
||||
c = _compilation_defaults(hs, cc, java, dep_info, plugin_dep_info, srcs, import_dir_map, extra_srcs, user_compile_flags, with_profiling, my_pkg_id = my_pkg_id, version = my_pkg_id.version, plugins = plugins)
|
||||
if with_shared:
|
||||
c.args.add("-dynamic-too")
|
||||
|
||||
coverage_data = []
|
||||
if hs.coverage_enabled:
|
||||
c.args.add_all(_hpc_compiler_args(hs))
|
||||
for src_file in srcs:
|
||||
pkg_id_string = pkg_id.to_string(my_pkg_id)
|
||||
module = module_name(hs, src_file)
|
||||
mix_file = hs.actions.declare_file(".hpc/{pkg}/{module}.mix".format(pkg = pkg_id_string, module = module))
|
||||
coverage_data.append(_coverage_datum(mix_file, src_file, hs.label))
|
||||
|
||||
hs.toolchain.actions.run_ghc(
|
||||
hs,
|
||||
cc,
|
||||
inputs = c.inputs,
|
||||
input_manifests = c.input_manifests,
|
||||
outputs = c.outputs + [datum.mix_file for datum in coverage_data],
|
||||
mnemonic = "HaskellBuildLibrary" + ("Prof" if with_profiling else ""),
|
||||
progress_message = "HaskellBuildLibrary {}".format(hs.label),
|
||||
env = c.env,
|
||||
arguments = c.args,
|
||||
)
|
||||
|
||||
if with_profiling:
|
||||
exposed_modules_file = None
|
||||
else:
|
||||
hidden_modules_file = hs.actions.declare_file(
|
||||
target_unique_name(hs, "hidden-modules"),
|
||||
)
|
||||
hs.actions.write(
|
||||
output = hidden_modules_file,
|
||||
content = ", ".join(other_modules),
|
||||
)
|
||||
reexported_modules_file = hs.actions.declare_file(
|
||||
target_unique_name(hs, "reexported-modules"),
|
||||
)
|
||||
hs.actions.write(
|
||||
output = reexported_modules_file,
|
||||
content = ", ".join(exposed_modules_reexports),
|
||||
)
|
||||
exposed_modules_file = hs.actions.declare_file(
|
||||
target_unique_name(hs, "exposed-modules"),
|
||||
)
|
||||
hs.actions.run(
|
||||
inputs = [
|
||||
c.interfaces_dir,
|
||||
hs.toolchain.global_pkg_db,
|
||||
hidden_modules_file,
|
||||
reexported_modules_file,
|
||||
],
|
||||
outputs = [exposed_modules_file],
|
||||
executable = ls_modules,
|
||||
arguments = [
|
||||
c.interfaces_dir.path,
|
||||
hs.toolchain.global_pkg_db.path,
|
||||
hidden_modules_file.path,
|
||||
reexported_modules_file.path,
|
||||
exposed_modules_file.path,
|
||||
],
|
||||
use_default_shell_env = True,
|
||||
)
|
||||
|
||||
return struct(
|
||||
interfaces_dir = c.interfaces_dir,
|
||||
objects_dir = c.objects_dir,
|
||||
compile_flags = c.compile_flags,
|
||||
source_files = c.source_files,
|
||||
extra_source_files = c.extra_source_files,
|
||||
import_dirs = c.import_dirs,
|
||||
exposed_modules_file = exposed_modules_file,
|
||||
coverage_data = coverage_data,
|
||||
)
|
||||
Loading…
Add table
Add a link
Reference in a new issue