diff options
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.bzl | 210 |
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 000000000000..1c438e8445a5 --- /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 |