about summary refs log tree commit diff
path: root/third_party/bazel/rules_haskell/haskell/private/actions/package.bzl
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/bazel/rules_haskell/haskell/private/actions/package.bzl')
-rw-r--r--third_party/bazel/rules_haskell/haskell/private/actions/package.bzl210
1 files changed, 210 insertions, 0 deletions
diff --git a/third_party/bazel/rules_haskell/haskell/private/actions/package.bzl b/third_party/bazel/rules_haskell/haskell/private/actions/package.bzl
new file mode 100644
index 0000000000..1c438e8445
--- /dev/null
+++ b/third_party/bazel/rules_haskell/haskell/private/actions/package.bzl
@@ -0,0 +1,210 @@
+"""Action for creating packages and registering them with ghc-pkg"""
+
+load("@bazel_skylib//lib:paths.bzl", "paths")
+load(":private/path_utils.bzl", "target_unique_name")
+load(":private/pkg_id.bzl", "pkg_id")
+load(":private/set.bzl", "set")
+load(":private/path_utils.bzl", "get_lib_name")
+
+def _get_extra_libraries(dep_info):
+    """Get directories and library names for extra library dependencies.
+
+    Args:
+      dep_info: HaskellInfo provider of the package.
+
+    Returns:
+      (dirs, libs):
+      dirs: list: Library search directories for extra library dependencies.
+      libs: list: Extra library dependencies.
+    """
+    cc_libs = dep_info.cc_dependencies.dynamic_linking.libraries_to_link.to_list()
+
+    # The order in which library dependencies are listed is relevant when
+    # linking static archives. To maintain the order defined by the input
+    # depset we collect the library dependencies in a list, and use a separate
+    # set to deduplicate entries.
+    seen_libs = set.empty()
+    extra_libs = []
+    extra_lib_dirs = set.empty()
+    for lib in cc_libs:
+        lib_name = get_lib_name(lib)
+        if not set.is_member(seen_libs, lib_name):
+            set.mutable_insert(seen_libs, lib_name)
+            extra_libs.append(lib_name)
+        set.mutable_insert(extra_lib_dirs, lib.dirname)
+    return (set.to_list(extra_lib_dirs), extra_libs)
+
+def package(
+        hs,
+        dep_info,
+        interfaces_dir,
+        interfaces_dir_prof,
+        static_library,
+        dynamic_library,
+        exposed_modules_file,
+        other_modules,
+        my_pkg_id,
+        static_library_prof):
+    """Create GHC package using ghc-pkg.
+
+    Args:
+      hs: Haskell context.
+      interfaces_dir: Directory containing interface files.
+      static_library: Static library of the package.
+      dynamic_library: Dynamic library of the package.
+      static_library_prof: Static library compiled with profiling or None.
+
+    Returns:
+      (File, File): GHC package conf file, GHC package cache file
+    """
+    pkg_db_dir = pkg_id.to_string(my_pkg_id)
+    conf_file = hs.actions.declare_file(
+        paths.join(pkg_db_dir, "{0}.conf".format(pkg_db_dir)),
+    )
+    cache_file = hs.actions.declare_file("package.cache", sibling = conf_file)
+
+    import_dir = paths.join(
+        "${pkgroot}",
+        paths.join(pkg_db_dir, "_iface"),
+    )
+    interfaces_dirs = [interfaces_dir]
+
+    if interfaces_dir_prof != None:
+        import_dir_prof = paths.join(
+            "${pkgroot}",
+            paths.join(pkg_db_dir, "_iface_prof"),
+        )
+        interfaces_dirs.append(interfaces_dir_prof)
+    else:
+        import_dir_prof = ""
+
+    (extra_lib_dirs, extra_libs) = _get_extra_libraries(dep_info)
+
+    metadata_entries = {
+        "name": my_pkg_id.name,
+        "version": my_pkg_id.version,
+        "id": pkg_id.to_string(my_pkg_id),
+        "key": pkg_id.to_string(my_pkg_id),
+        "exposed": "True",
+        "hidden-modules": " ".join(other_modules),
+        "import-dirs": " ".join([import_dir, import_dir_prof]),
+        "library-dirs": " ".join(["${pkgroot}"] + extra_lib_dirs),
+        "dynamic-library-dirs": " ".join(["${pkgroot}"] + extra_lib_dirs),
+        "hs-libraries": pkg_id.library_name(hs, my_pkg_id),
+        "extra-libraries": " ".join(extra_libs),
+        "depends": ", ".join(
+            # Prebuilt dependencies are added further down, since their
+            # package-ids are not available as strings but in build outputs.
+            set.to_list(dep_info.package_ids),
+        ),
+    }
+
+    # Create a file from which ghc-pkg will create the actual package
+    # from. List of exposed modules generated below.
+    metadata_file = hs.actions.declare_file(target_unique_name(hs, "metadata"))
+    hs.actions.write(
+        output = metadata_file,
+        content = "\n".join([
+            "{0}: {1}".format(k, v)
+            for k, v in metadata_entries.items()
+            if v
+        ]) + "\n",
+    )
+
+    # Collect the package id files of all prebuilt dependencies.
+    prebuilt_deps_id_files = [
+        dep.id_file
+        for dep in set.to_list(dep_info.prebuilt_dependencies)
+    ]
+
+    # Combine exposed modules and other metadata to form the package
+    # configuration file.
+
+    prebuilt_deps_args = hs.actions.args()
+    prebuilt_deps_args.add_all([f.path for f in prebuilt_deps_id_files])
+    prebuilt_deps_args.use_param_file("%s", use_always = True)
+    prebuilt_deps_args.set_param_file_format("multiline")
+
+    hs.actions.run_shell(
+        inputs = [metadata_file, exposed_modules_file] + prebuilt_deps_id_files,
+        outputs = [conf_file],
+        command = """
+            cat $1 > $4
+            echo "exposed-modules: `cat $2`" >> $4
+
+            # this is equivalent to 'readarray'. We do use 'readarray' in order to
+            # support older bash versions.
+            while IFS= read -r line; do deps_id_files+=("$line"); done < $3
+
+            if [ ${#deps_id_files[@]} -eq 0 ]; then
+              deps=""
+            else
+              deps=$(cat "${deps_id_files[@]}" | tr '\n' " ")
+            fi
+            echo "depends: $deps" >> $4
+""",
+        arguments = [
+            metadata_file.path,
+            exposed_modules_file.path,
+            prebuilt_deps_args,
+            conf_file.path,
+        ],
+        use_default_shell_env = True,
+    )
+
+    # Make the call to ghc-pkg and use the package configuration file
+    package_path = ":".join([c.dirname for c in set.to_list(dep_info.package_databases)]) + ":"
+    hs.actions.run(
+        inputs = depset(transitive = [
+            set.to_depset(dep_info.package_databases),
+            depset(interfaces_dirs),
+            depset([
+                input
+                for input in [
+                    static_library,
+                    conf_file,
+                    dynamic_library,
+                    static_library_prof,
+                ]
+                if input
+            ]),
+        ]),
+        outputs = [cache_file],
+        env = {
+            "GHC_PACKAGE_PATH": package_path,
+        },
+        mnemonic = "HaskellRegisterPackage",
+        progress_message = "HaskellRegisterPackage {}".format(hs.label),
+        executable = hs.tools.ghc_pkg,
+        # Registration of a new package consists in,
+        #
+        # 1. copying the registration file into the package db,
+        # 2. performing some validation on the registration file content,
+        # 3. recaching, i.e. regenerating the package db cache file.
+        #
+        # Normally, this is all done by `ghc-pkg register`. But in our
+        # case, `ghc-pkg register` is painful, because the validation
+        # it performs is slow, somewhat redundant but especially, too
+        # strict (see e.g.
+        # https://ghc.haskell.org/trac/ghc/ticket/15478). So we do (1)
+        # and (3) manually, by copying then calling `ghc-pkg recache`
+        # directly.
+        #
+        # The downside is that we do lose the few validations that
+        # `ghc-pkg register` was doing that was useful. e.g. when
+        # reexporting modules, validation checks that the source
+        # module does exist.
+        #
+        # TODO Go back to using `ghc-pkg register`. Blocked by
+        # https://ghc.haskell.org/trac/ghc/ticket/15478
+        arguments = [
+            "recache",
+            "--package-db={0}".format(conf_file.dirname),
+            "-v0",
+            "--no-expand-pkgroot",
+        ],
+        # XXX: Seems required for this to work on Windows
+        use_default_shell_env = True,
+    )
+
+    return conf_file, cache_file