about summary refs log tree commit diff
path: root/third_party/bazel/rules_haskell/haskell/private/actions/compile.bzl
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/bazel/rules_haskell/haskell/private/actions/compile.bzl')
-rw-r--r--third_party/bazel/rules_haskell/haskell/private/actions/compile.bzl563
1 files changed, 563 insertions, 0 deletions
diff --git a/third_party/bazel/rules_haskell/haskell/private/actions/compile.bzl b/third_party/bazel/rules_haskell/haskell/private/actions/compile.bzl
new file mode 100644
index 0000000000..530b23a04b
--- /dev/null
+++ b/third_party/bazel/rules_haskell/haskell/private/actions/compile.bzl
@@ -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,
+    )