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, | ||||
|     ) | ||||
							
								
								
									
										667
									
								
								third_party/bazel/rules_haskell/haskell/private/actions/link.bzl
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										667
									
								
								third_party/bazel/rules_haskell/haskell/private/actions/link.bzl
									
										
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,667 @@ | |||
| """Actions for linking object code produced by compilation""" | ||||
| 
 | ||||
| load(":private/packages.bzl", "expose_packages", "pkg_info_to_compile_flags") | ||||
| load("@bazel_skylib//lib:paths.bzl", "paths") | ||||
| load( | ||||
|     ":private/path_utils.bzl", | ||||
|     "get_lib_name", | ||||
|     "is_shared_library", | ||||
|     "is_static_library", | ||||
|     "ln", | ||||
| ) | ||||
| load(":private/pkg_id.bzl", "pkg_id") | ||||
| load(":private/set.bzl", "set") | ||||
| load(":private/list.bzl", "list") | ||||
| 
 | ||||
| # tests in /tests/unit_tests/BUILD | ||||
| def parent_dir_path(path): | ||||
|     """Returns the path of the parent directory. | ||||
|     For a relative path with just a file, "." is returned. | ||||
|     The path is not normalized. | ||||
| 
 | ||||
|     foo => . | ||||
|     foo/ => foo | ||||
|     foo/bar => foo | ||||
|     foo/bar/baz => foo/bar | ||||
|     foo/../bar => foo/.. | ||||
| 
 | ||||
|     Args: | ||||
|       a path string | ||||
| 
 | ||||
|     Returns: | ||||
|       A path list of the form `["foo", "bar"]` | ||||
|     """ | ||||
|     path_dir = paths.dirname(path) | ||||
| 
 | ||||
|     # dirname returns "" if there is no parent directory | ||||
|     # In that case we return the identity path, which is ".". | ||||
|     if path_dir == "": | ||||
|         return ["."] | ||||
|     else: | ||||
|         return path_dir.split("/") | ||||
| 
 | ||||
| def __check_dots(target, path): | ||||
|     # there’s still (non-leading) .. in split | ||||
|     if ".." in path: | ||||
|         fail("the short_path of target {} (which is {}) contains more dots than loading `../`. We can’t handle that.".format( | ||||
|             target, | ||||
|             target.short_path, | ||||
|         )) | ||||
| 
 | ||||
| # skylark doesn’t allow nested defs, which is a mystery. | ||||
| def _get_target_parent_dir(target): | ||||
|     """get the parent dir and handle leading short_path dots, | ||||
|     which signify that the target is in an external repository. | ||||
| 
 | ||||
|     Args: | ||||
|       target: a target, .short_path is used | ||||
|     Returns: | ||||
|       (is_external, parent_dir) | ||||
|       `is_external`: Bool whether the path points to an external repository | ||||
|       `parent_dir`: The parent directory, either up to the runfiles toplel, | ||||
|                     up to the external repository toplevel. | ||||
|                     Is `[]` if there is no parent dir. | ||||
|     """ | ||||
| 
 | ||||
|     parent_dir = parent_dir_path(target.short_path) | ||||
| 
 | ||||
|     if parent_dir[0] == "..": | ||||
|         __check_dots(target, parent_dir[1:]) | ||||
|         return (True, parent_dir[1:]) | ||||
|     elif parent_dir[0] == ".": | ||||
|         return (False, []) | ||||
|     else: | ||||
|         __check_dots(target, parent_dir) | ||||
|         return (False, parent_dir) | ||||
| 
 | ||||
| # tests in /tests/unit_tests/BUILD | ||||
| def create_rpath_entry( | ||||
|         binary, | ||||
|         dependency, | ||||
|         keep_filename, | ||||
|         prefix = ""): | ||||
|     """Return a (relative) path that points from `binary` to `dependecy` | ||||
|     while not leaving the current bazel runpath, taking into account weird | ||||
|     corner cases of `.short_path` concerning external repositories. | ||||
|     The resulting entry should be able to be inserted into rpath or similar. | ||||
| 
 | ||||
|     Examples: | ||||
| 
 | ||||
|       bin.short_path=foo/a.so and dep.short_path=bar/b.so | ||||
|         => create_rpath_entry(bin, dep, False) = ../bar | ||||
|            and | ||||
|            create_rpath_entry(bin, dep, True) = ../bar/b.so | ||||
|            and | ||||
|            create_rpath_entry(bin, dep, True, "$ORIGIN") = $ORIGIN/../bar/b.so | ||||
| 
 | ||||
|     Args: | ||||
|       binary: target of current binary | ||||
|       dependency: target of dependency to relatively point to | ||||
|       keep_filename: whether to point to the filename or its parent dir | ||||
|       prefix: string path prefix to add before the relative path | ||||
| 
 | ||||
|     Returns: | ||||
|       relative path string | ||||
|     """ | ||||
| 
 | ||||
|     (bin_is_external, bin_parent_dir) = _get_target_parent_dir(binary) | ||||
|     (dep_is_external, dep_parent_dir) = _get_target_parent_dir(dependency) | ||||
| 
 | ||||
|     # backup through parent directories of the binary, | ||||
|     # to the runfiles directory | ||||
|     bin_backup = [".."] * len(bin_parent_dir) | ||||
| 
 | ||||
|     # external repositories live in `target.runfiles/external`, | ||||
|     # while the internal repository lives in `target.runfiles`. | ||||
|     # The `.short_path`s of external repositories are strange, | ||||
|     # they start with `../`, but you cannot just append that in | ||||
|     # order to find the correct runpath. Instead you have to use | ||||
|     # the following logic to construct the correct runpaths: | ||||
|     if bin_is_external: | ||||
|         if dep_is_external: | ||||
|             # stay in `external` | ||||
|             path_segments = bin_backup | ||||
|         else: | ||||
|             # backup out of `external` | ||||
|             path_segments = [".."] + bin_backup | ||||
|     elif dep_is_external: | ||||
|         # go into `external` | ||||
|         path_segments = bin_backup + ["external"] | ||||
|     else: | ||||
|         # no special external traversal | ||||
|         path_segments = bin_backup | ||||
| 
 | ||||
|     # then add the parent dir to our dependency | ||||
|     path_segments.extend(dep_parent_dir) | ||||
| 
 | ||||
|     # optionally add the filename | ||||
|     if keep_filename: | ||||
|         path_segments.append( | ||||
|             paths.basename(dependency.short_path), | ||||
|         ) | ||||
| 
 | ||||
|     # normalize for good measure and create the final path | ||||
|     path = paths.normalize("/".join(path_segments)) | ||||
| 
 | ||||
|     # and add the prefix if applicable | ||||
|     if prefix == "": | ||||
|         return path | ||||
|     else: | ||||
|         return prefix + "/" + path | ||||
| 
 | ||||
| def _merge_parameter_files(hs, file1, file2): | ||||
|     """Merge two GHC parameter files into one. | ||||
| 
 | ||||
|     Args: | ||||
|       hs: Haskell context. | ||||
|       file1: The first parameter file. | ||||
|       file2: The second parameter file. | ||||
| 
 | ||||
|     Returns: | ||||
|       File: A new parameter file containing the parameters of both input files. | ||||
|         The file name is based on the file names of the input files. The file | ||||
|         is located next to the first input file. | ||||
|     """ | ||||
|     params_file = hs.actions.declare_file( | ||||
|         file1.basename + ".and." + file2.basename, | ||||
|         sibling = file1, | ||||
|     ) | ||||
|     hs.actions.run_shell( | ||||
|         inputs = [file1, file2], | ||||
|         outputs = [params_file], | ||||
|         command = """ | ||||
|             cat {file1} {file2} > {out} | ||||
|         """.format( | ||||
|             file1 = file1.path, | ||||
|             file2 = file2.path, | ||||
|             out = params_file.path, | ||||
|         ), | ||||
|     ) | ||||
|     return params_file | ||||
| 
 | ||||
| def _darwin_create_extra_linker_flags_file(hs, cc, objects_dir, executable, dynamic, solibs): | ||||
|     """Write additional linker flags required on MacOS to a parameter file. | ||||
| 
 | ||||
|     Args: | ||||
|       hs: Haskell context. | ||||
|       cc: CcInteropInfo, information about C dependencies. | ||||
|       objects_dir: Directory storing object files. | ||||
|         Used to determine output file location. | ||||
|       executable: The executable being built. | ||||
|       dynamic: Bool: Whether to link dynamically or statically. | ||||
|       solibs: List of dynamic library dependencies. | ||||
| 
 | ||||
|     Returns: | ||||
|       File: Parameter file with additional linker flags. To be passed to GHC. | ||||
|     """ | ||||
| 
 | ||||
|     # On Darwin GHC will pass the dead_strip_dylibs flag to the linker. This | ||||
|     # flag will remove any shared library loads from the binary's header that | ||||
|     # are not directly resolving undefined symbols in the binary. I.e. any | ||||
|     # indirect shared library dependencies will be removed. This conflicts with | ||||
|     # Bazel's builtin cc rules, which assume that the final binary will load | ||||
|     # all transitive shared library dependencies. In particlar shared libraries | ||||
|     # produced by Bazel's cc rules never load shared libraries themselves. This | ||||
|     # causes missing symbols at runtime on MacOS, see #170. | ||||
|     # | ||||
|     # The following work-around applies the `-u` flag to the linker for any | ||||
|     # symbol that is undefined in any transitive shared library dependency. | ||||
|     # This forces the linker to resolve these undefined symbols in all | ||||
|     # transitive shared library dependencies and keep the corresponding load | ||||
|     # commands in the binary's header. | ||||
|     # | ||||
|     # Unfortunately, this prohibits elimination of any truly redundant shared | ||||
|     # library dependencies. Furthermore, the transitive closure of shared | ||||
|     # library dependencies can be large, so this makes it more likely to exceed | ||||
|     # the MACH-O header size limit on MacOS. | ||||
|     # | ||||
|     # This is a horrendous hack, but it seems to be forced on us by how Bazel | ||||
|     # builds dynamic cc libraries. | ||||
|     suffix = ".dynamic.linker_flags" if dynamic else ".static.linker_flags" | ||||
|     linker_flags_file = hs.actions.declare_file( | ||||
|         executable.basename + suffix, | ||||
|         sibling = objects_dir, | ||||
|     ) | ||||
| 
 | ||||
|     hs.actions.run_shell( | ||||
|         inputs = solibs, | ||||
|         outputs = [linker_flags_file], | ||||
|         command = """ | ||||
|         touch {out} | ||||
|         for lib in {solibs}; do | ||||
|             {nm} -u "$lib" | sed 's/^/-optl-Wl,-u,/' >> {out} | ||||
|         done | ||||
|         """.format( | ||||
|             nm = cc.tools.nm, | ||||
|             solibs = " ".join(["\"" + l.path + "\"" for l in solibs]), | ||||
|             out = linker_flags_file.path, | ||||
|         ), | ||||
|     ) | ||||
|     return linker_flags_file | ||||
| 
 | ||||
| def _create_objects_dir_manifest(hs, objects_dir, dynamic, with_profiling): | ||||
|     suffix = ".dynamic.manifest" if dynamic else ".static.manifest" | ||||
|     objects_dir_manifest = hs.actions.declare_file( | ||||
|         objects_dir.basename + suffix, | ||||
|         sibling = objects_dir, | ||||
|     ) | ||||
| 
 | ||||
|     if with_profiling: | ||||
|         ext = "p_o" | ||||
|     elif dynamic: | ||||
|         ext = "dyn_o" | ||||
|     else: | ||||
|         ext = "o" | ||||
|     hs.actions.run_shell( | ||||
|         inputs = [objects_dir], | ||||
|         outputs = [objects_dir_manifest], | ||||
|         command = """ | ||||
|         find {dir} -name '*.{ext}' > {out} | ||||
|         """.format( | ||||
|             dir = objects_dir.path, | ||||
|             ext = ext, | ||||
|             out = objects_dir_manifest.path, | ||||
|         ), | ||||
|         use_default_shell_env = True, | ||||
|     ) | ||||
| 
 | ||||
|     return objects_dir_manifest | ||||
| 
 | ||||
| def _link_dependencies(hs, dep_info, dynamic, binary, args): | ||||
|     """Configure linker flags and inputs. | ||||
| 
 | ||||
|     Configure linker flags for C library dependencies and runtime dynamic | ||||
|     library dependencies. And collect the C libraries to pass as inputs to | ||||
|     the linking action. | ||||
| 
 | ||||
|     Args: | ||||
|       hs: Haskell context. | ||||
|       dep_info: HaskellInfo provider. | ||||
|       dynamic: Bool: Whether to link dynamically, or statically. | ||||
|       binary: Final linked binary. | ||||
|       args: Arguments to the linking action. | ||||
| 
 | ||||
|     Returns: | ||||
|       depset: C library dependencies to provide as input to the linking action. | ||||
|     """ | ||||
| 
 | ||||
|     # Pick linking context based on linking mode. | ||||
|     if dynamic: | ||||
|         link_ctx = dep_info.cc_dependencies.dynamic_linking | ||||
|         trans_link_ctx = dep_info.transitive_cc_dependencies.dynamic_linking | ||||
|     else: | ||||
|         link_ctx = dep_info.cc_dependencies.static_linking | ||||
|         trans_link_ctx = dep_info.transitive_cc_dependencies.static_linking | ||||
| 
 | ||||
|     # Direct C library dependencies to link. | ||||
|     # I.e. not indirect through another Haskell dependency. | ||||
|     # Such indirect dependencies are linked by GHC based on the extra-libraries | ||||
|     # fields in the dependency's package configuration file. | ||||
|     libs_to_link = link_ctx.libraries_to_link.to_list() | ||||
|     _add_external_libraries(args, libs_to_link) | ||||
| 
 | ||||
|     # Transitive library dependencies to have in scope for linking. | ||||
|     trans_libs_to_link = trans_link_ctx.libraries_to_link.to_list() | ||||
| 
 | ||||
|     # Libraries to pass as inputs to linking action. | ||||
|     cc_link_libs = depset(transitive = [ | ||||
|         depset(trans_libs_to_link), | ||||
|     ]) | ||||
| 
 | ||||
|     # Transitive dynamic library dependencies to have in RUNPATH. | ||||
|     cc_solibs = trans_link_ctx.dynamic_libraries_for_runtime.to_list() | ||||
| 
 | ||||
|     # Collect Haskell dynamic library dependencies in common RUNPATH. | ||||
|     # This is to keep the number of RUNPATH entries low, for faster loading | ||||
|     # and to avoid exceeding the MACH-O header size limit on MacOS. | ||||
|     hs_solibs = [] | ||||
|     if dynamic: | ||||
|         hs_solibs_prefix = "_hssolib_%s" % hs.name | ||||
|         for dep in set.to_list(dep_info.dynamic_libraries): | ||||
|             dep_link = hs.actions.declare_file( | ||||
|                 paths.join(hs_solibs_prefix, dep.basename), | ||||
|                 sibling = binary, | ||||
|             ) | ||||
|             ln(hs, dep, dep_link) | ||||
|             hs_solibs.append(dep_link) | ||||
| 
 | ||||
|     # Configure RUNPATH. | ||||
|     rpaths = _infer_rpaths( | ||||
|         hs.toolchain.is_darwin, | ||||
|         binary, | ||||
|         trans_link_ctx.dynamic_libraries_for_runtime.to_list() + | ||||
|         hs_solibs, | ||||
|     ) | ||||
|     for rpath in set.to_list(rpaths): | ||||
|         args.add("-optl-Wl,-rpath," + rpath) | ||||
| 
 | ||||
|     return (cc_link_libs, cc_solibs, hs_solibs) | ||||
| 
 | ||||
| def link_binary( | ||||
|         hs, | ||||
|         cc, | ||||
|         dep_info, | ||||
|         extra_srcs, | ||||
|         compiler_flags, | ||||
|         objects_dir, | ||||
|         dynamic, | ||||
|         with_profiling, | ||||
|         version): | ||||
|     """Link Haskell binary from static object files. | ||||
| 
 | ||||
|     Returns: | ||||
|       File: produced executable | ||||
|     """ | ||||
| 
 | ||||
|     exe_name = hs.name + (".exe" if hs.toolchain.is_windows else "") | ||||
|     executable = hs.actions.declare_file(exe_name) | ||||
| 
 | ||||
|     args = hs.actions.args() | ||||
|     args.add_all(["-optl" + f for f in cc.linker_flags]) | ||||
|     if with_profiling: | ||||
|         args.add("-prof") | ||||
|     args.add_all(hs.toolchain.compiler_flags) | ||||
|     args.add_all(compiler_flags) | ||||
| 
 | ||||
|     # By default, GHC will produce mostly-static binaries, i.e. in which all | ||||
|     # Haskell code is statically linked and foreign libraries and system | ||||
|     # dependencies are dynamically linked. If linkstatic is false, i.e. the user | ||||
|     # has requested fully dynamic linking, we must therefore add flags to make | ||||
|     # sure that GHC dynamically links Haskell code too. The one exception to | ||||
|     # this is when we are compiling for profiling, which currently does not play | ||||
|     # nicely with dynamic linking. | ||||
|     if dynamic: | ||||
|         if with_profiling: | ||||
|             print("WARNING: dynamic linking and profiling don't mix. Omitting -dynamic.\nSee https://ghc.haskell.org/trac/ghc/ticket/15394") | ||||
|         else: | ||||
|             args.add_all(["-pie", "-dynamic"]) | ||||
| 
 | ||||
|     # When compiling with `-threaded`, GHC needs to link against | ||||
|     # the pthread library when linking against static archives (.a). | ||||
|     # We assume it’s not a problem to pass it for other cases, | ||||
|     # so we just default to passing it. | ||||
|     args.add("-optl-pthread") | ||||
| 
 | ||||
|     args.add_all(["-o", executable.path]) | ||||
| 
 | ||||
|     # De-duplicate optl calls while preserving ordering: we want last | ||||
|     # invocation of an object to remain last. That is `-optl foo -optl | ||||
|     # bar -optl foo` becomes `-optl bar -optl foo`. Do this by counting | ||||
|     # number of occurrences. That way we only build dict and add to args | ||||
|     # directly rather than doing multiple reversals with temporary | ||||
|     # lists. | ||||
| 
 | ||||
|     args.add_all(pkg_info_to_compile_flags(expose_packages( | ||||
|         dep_info, | ||||
|         lib_info = None, | ||||
|         use_direct = True, | ||||
|         use_my_pkg_id = None, | ||||
|         custom_package_databases = None, | ||||
|         version = version, | ||||
|     ))) | ||||
| 
 | ||||
|     (cc_link_libs, cc_solibs, hs_solibs) = _link_dependencies( | ||||
|         hs = hs, | ||||
|         dep_info = dep_info, | ||||
|         dynamic = dynamic, | ||||
|         binary = executable, | ||||
|         args = args, | ||||
|     ) | ||||
| 
 | ||||
|     # XXX: Suppress a warning that Clang prints due to GHC automatically passing | ||||
|     # "-pie" or "-no-pie" to the C compiler. | ||||
|     # This is linked to https://ghc.haskell.org/trac/ghc/ticket/15319 | ||||
|     args.add_all([ | ||||
|         "-optc-Wno-unused-command-line-argument", | ||||
|         "-optl-Wno-unused-command-line-argument", | ||||
|     ]) | ||||
| 
 | ||||
|     objects_dir_manifest = _create_objects_dir_manifest( | ||||
|         hs, | ||||
|         objects_dir, | ||||
|         dynamic = dynamic, | ||||
|         with_profiling = with_profiling, | ||||
|     ) | ||||
| 
 | ||||
|     extra_linker_flags_file = None | ||||
|     if hs.toolchain.is_darwin: | ||||
|         args.add("-optl-Wl,-headerpad_max_install_names") | ||||
| 
 | ||||
|         # Nixpkgs commit 3513034208a introduces -liconv in NIX_LDFLAGS on | ||||
|         # Darwin. We don't currently handle NIX_LDFLAGS in any special | ||||
|         # way, so a hack is to simply do what NIX_LDFLAGS is telling us we | ||||
|         # should do always when using a toolchain from Nixpkgs. | ||||
|         # TODO remove this gross hack. | ||||
|         args.add("-liconv") | ||||
| 
 | ||||
|         extra_linker_flags_file = _darwin_create_extra_linker_flags_file( | ||||
|             hs, | ||||
|             cc, | ||||
|             objects_dir, | ||||
|             executable, | ||||
|             dynamic, | ||||
|             cc_solibs, | ||||
|         ) | ||||
| 
 | ||||
|     if extra_linker_flags_file != None: | ||||
|         params_file = _merge_parameter_files(hs, objects_dir_manifest, extra_linker_flags_file) | ||||
|     else: | ||||
|         params_file = objects_dir_manifest | ||||
| 
 | ||||
|     hs.toolchain.actions.run_ghc( | ||||
|         hs, | ||||
|         cc, | ||||
|         inputs = depset(transitive = [ | ||||
|             depset(extra_srcs), | ||||
|             set.to_depset(dep_info.package_databases), | ||||
|             set.to_depset(dep_info.dynamic_libraries), | ||||
|             depset(dep_info.static_libraries), | ||||
|             depset(dep_info.static_libraries_prof), | ||||
|             depset([objects_dir]), | ||||
|             cc_link_libs, | ||||
|         ]), | ||||
|         outputs = [executable], | ||||
|         mnemonic = "HaskellLinkBinary", | ||||
|         arguments = args, | ||||
|         params_file = params_file, | ||||
|     ) | ||||
| 
 | ||||
|     return (executable, cc_solibs + hs_solibs) | ||||
| 
 | ||||
| def _add_external_libraries(args, ext_libs): | ||||
|     """Add options to `args` that allow us to link to `ext_libs`. | ||||
| 
 | ||||
|     Args: | ||||
|       args: Args object. | ||||
|       ext_libs: C library dependencies. | ||||
|     """ | ||||
| 
 | ||||
|     # Deduplicate the list of ext_libs based on their | ||||
|     # library name (file name stripped of lib prefix and endings). | ||||
|     # This keeps the command lines short, e.g. when a C library | ||||
|     # like `liblz4.so` appears in multiple dependencies. | ||||
|     # XXX: this is only done in here | ||||
|     # Shouldn’t the deduplication be applied to *all* external libraries? | ||||
|     deduped = list.dedup_on(get_lib_name, ext_libs) | ||||
| 
 | ||||
|     for lib in deduped: | ||||
|         args.add_all([ | ||||
|             "-L{0}".format( | ||||
|                 paths.dirname(lib.path), | ||||
|             ), | ||||
|             "-l{0}".format( | ||||
|                 # technically this is the second call to get_lib_name, | ||||
|                 #  but the added clarity makes up for it. | ||||
|                 get_lib_name(lib), | ||||
|             ), | ||||
|         ]) | ||||
| 
 | ||||
| def _infer_rpaths(is_darwin, target, solibs): | ||||
|     """Return set of RPATH values to be added to target so it can find all | ||||
|     solibs | ||||
| 
 | ||||
|     The resulting paths look like: | ||||
|     $ORIGIN/../../path/to/solib/dir | ||||
|     This means: "go upwards to your runfiles directory, then descend into | ||||
|     the parent folder of the solib". | ||||
| 
 | ||||
|     Args: | ||||
|       is_darwin: Whether we're compiling on and for Darwin. | ||||
|       target: File, executable or library we're linking. | ||||
|       solibs: A list of Files, shared objects that the target needs. | ||||
| 
 | ||||
|     Returns: | ||||
|       Set of strings: rpaths to add to target. | ||||
|     """ | ||||
|     r = set.empty() | ||||
| 
 | ||||
|     if is_darwin: | ||||
|         prefix = "@loader_path" | ||||
|     else: | ||||
|         prefix = "$ORIGIN" | ||||
| 
 | ||||
|     for solib in solibs: | ||||
|         rpath = create_rpath_entry( | ||||
|             binary = target, | ||||
|             dependency = solib, | ||||
|             keep_filename = False, | ||||
|             prefix = prefix, | ||||
|         ) | ||||
|         set.mutable_insert(r, rpath) | ||||
| 
 | ||||
|     return r | ||||
| 
 | ||||
| def _so_extension(hs): | ||||
|     """Returns the extension for shared libraries. | ||||
| 
 | ||||
|     Args: | ||||
|       hs: Haskell rule context. | ||||
| 
 | ||||
|     Returns: | ||||
|       string of extension. | ||||
|     """ | ||||
|     return "dylib" if hs.toolchain.is_darwin else "so" | ||||
| 
 | ||||
| def link_library_static(hs, cc, dep_info, objects_dir, my_pkg_id, with_profiling): | ||||
|     """Link a static library for the package using given object files. | ||||
| 
 | ||||
|     Returns: | ||||
|       File: Produced static library. | ||||
|     """ | ||||
|     static_library = hs.actions.declare_file( | ||||
|         "lib{0}.a".format(pkg_id.library_name(hs, my_pkg_id, prof_suffix = with_profiling)), | ||||
|     ) | ||||
|     objects_dir_manifest = _create_objects_dir_manifest( | ||||
|         hs, | ||||
|         objects_dir, | ||||
|         dynamic = False, | ||||
|         with_profiling = with_profiling, | ||||
|     ) | ||||
|     args = hs.actions.args() | ||||
|     inputs = [objects_dir, objects_dir_manifest] + cc.files | ||||
| 
 | ||||
|     if hs.toolchain.is_darwin: | ||||
|         # On Darwin, ar doesn't support params files. | ||||
|         args.add_all([ | ||||
|             static_library, | ||||
|             objects_dir_manifest.path, | ||||
|         ]) | ||||
| 
 | ||||
|         # TODO Get ar location from the CC toolchain. This is | ||||
|         # complicated by the fact that the CC toolchain does not | ||||
|         # always use ar, and libtool has an entirely different CLI. | ||||
|         # See https://github.com/bazelbuild/bazel/issues/5127 | ||||
|         hs.actions.run_shell( | ||||
|             inputs = inputs, | ||||
|             outputs = [static_library], | ||||
|             mnemonic = "HaskellLinkStaticLibrary", | ||||
|             command = "{ar} qc $1 $(< $2)".format(ar = cc.tools.ar), | ||||
|             arguments = [args], | ||||
| 
 | ||||
|             # Use the default macosx toolchain | ||||
|             env = {"SDKROOT": "macosx"}, | ||||
|         ) | ||||
|     else: | ||||
|         args.add_all([ | ||||
|             "qc", | ||||
|             static_library, | ||||
|             "@" + objects_dir_manifest.path, | ||||
|         ]) | ||||
|         hs.actions.run( | ||||
|             inputs = inputs, | ||||
|             outputs = [static_library], | ||||
|             mnemonic = "HaskellLinkStaticLibrary", | ||||
|             executable = cc.tools.ar, | ||||
|             arguments = [args], | ||||
|         ) | ||||
| 
 | ||||
|     return static_library | ||||
| 
 | ||||
| def link_library_dynamic(hs, cc, dep_info, extra_srcs, objects_dir, my_pkg_id): | ||||
|     """Link a dynamic library for the package using given object files. | ||||
| 
 | ||||
|     Returns: | ||||
|       File: Produced dynamic library. | ||||
|     """ | ||||
| 
 | ||||
|     dynamic_library = hs.actions.declare_file( | ||||
|         "lib{0}-ghc{1}.{2}".format( | ||||
|             pkg_id.library_name(hs, my_pkg_id), | ||||
|             hs.toolchain.version, | ||||
|             _so_extension(hs), | ||||
|         ), | ||||
|     ) | ||||
| 
 | ||||
|     args = hs.actions.args() | ||||
|     args.add_all(["-optl" + f for f in cc.linker_flags]) | ||||
|     args.add_all(["-shared", "-dynamic"]) | ||||
| 
 | ||||
|     # 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: | ||||
|         args.add("-optl-Wl,-dead_strip_dylibs") | ||||
| 
 | ||||
|     args.add_all(pkg_info_to_compile_flags(expose_packages( | ||||
|         dep_info, | ||||
|         lib_info = None, | ||||
|         use_direct = True, | ||||
|         use_my_pkg_id = None, | ||||
|         custom_package_databases = None, | ||||
|         version = my_pkg_id.version if my_pkg_id else None, | ||||
|     ))) | ||||
| 
 | ||||
|     (cc_link_libs, _cc_solibs, _hs_solibs) = _link_dependencies( | ||||
|         hs = hs, | ||||
|         dep_info = dep_info, | ||||
|         dynamic = True, | ||||
|         binary = dynamic_library, | ||||
|         args = args, | ||||
|     ) | ||||
| 
 | ||||
|     args.add_all(["-o", dynamic_library.path]) | ||||
| 
 | ||||
|     # Profiling not supported for dynamic libraries. | ||||
|     objects_dir_manifest = _create_objects_dir_manifest( | ||||
|         hs, | ||||
|         objects_dir, | ||||
|         dynamic = True, | ||||
|         with_profiling = False, | ||||
|     ) | ||||
| 
 | ||||
|     hs.toolchain.actions.run_ghc( | ||||
|         hs, | ||||
|         cc, | ||||
|         inputs = depset([objects_dir], transitive = [ | ||||
|             depset(extra_srcs), | ||||
|             set.to_depset(dep_info.package_databases), | ||||
|             set.to_depset(dep_info.dynamic_libraries), | ||||
|             cc_link_libs, | ||||
|         ]), | ||||
|         outputs = [dynamic_library], | ||||
|         mnemonic = "HaskellLinkDynamicLibrary", | ||||
|         arguments = args, | ||||
|         params_file = objects_dir_manifest, | ||||
|     ) | ||||
| 
 | ||||
|     return dynamic_library | ||||
							
								
								
									
										210
									
								
								third_party/bazel/rules_haskell/haskell/private/actions/package.bzl
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										210
									
								
								third_party/bazel/rules_haskell/haskell/private/actions/package.bzl
									
										
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -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 | ||||
							
								
								
									
										175
									
								
								third_party/bazel/rules_haskell/haskell/private/actions/repl.bzl
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										175
									
								
								third_party/bazel/rules_haskell/haskell/private/actions/repl.bzl
									
										
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,175 @@ | |||
| """GHCi REPL support""" | ||||
| 
 | ||||
| load(":private/context.bzl", "render_env") | ||||
| load(":private/packages.bzl", "expose_packages", "pkg_info_to_compile_flags") | ||||
| load( | ||||
|     ":private/path_utils.bzl", | ||||
|     "get_lib_name", | ||||
|     "is_shared_library", | ||||
|     "link_libraries", | ||||
|     "ln", | ||||
|     "target_unique_name", | ||||
| ) | ||||
| load(":providers.bzl", "get_libs_for_ghc_linker") | ||||
| load( | ||||
|     ":private/set.bzl", | ||||
|     "set", | ||||
| ) | ||||
| load("@bazel_skylib//lib:paths.bzl", "paths") | ||||
| load("@bazel_skylib//lib:shell.bzl", "shell") | ||||
| 
 | ||||
| def build_haskell_repl( | ||||
|         hs, | ||||
|         ghci_script, | ||||
|         ghci_repl_wrapper, | ||||
|         user_compile_flags, | ||||
|         repl_ghci_args, | ||||
|         hs_info, | ||||
|         output, | ||||
|         package_databases, | ||||
|         version, | ||||
|         lib_info = None): | ||||
|     """Build REPL script. | ||||
| 
 | ||||
|     Args: | ||||
|       hs: Haskell context. | ||||
|       hs_info: HaskellInfo. | ||||
| 
 | ||||
|       package_databases: package caches excluding the cache file of the package | ||||
|                       we're creating a REPL for. | ||||
|       lib_info: If we're building REPL for a library target, pass | ||||
|                 HaskellLibraryInfo here, otherwise it should be None. | ||||
| 
 | ||||
|     Returns: | ||||
|       None. | ||||
|     """ | ||||
| 
 | ||||
|     # The base and directory packages are necessary for the GHCi script we use | ||||
|     # (loads source files and brings in scope the corresponding modules). | ||||
|     args = ["-package", "base", "-package", "directory"] | ||||
| 
 | ||||
|     pkg_ghc_info = expose_packages( | ||||
|         hs_info, | ||||
|         lib_info, | ||||
|         use_direct = False, | ||||
|         use_my_pkg_id = None, | ||||
|         custom_package_databases = package_databases, | ||||
|         version = version, | ||||
|     ) | ||||
|     args += pkg_info_to_compile_flags(pkg_ghc_info) | ||||
| 
 | ||||
|     lib_imports = [] | ||||
|     if lib_info != None: | ||||
|         for idir in set.to_list(hs_info.import_dirs): | ||||
|             args += ["-i{0}".format(idir)] | ||||
|             lib_imports.append(idir) | ||||
| 
 | ||||
|     link_ctx = hs_info.cc_dependencies.dynamic_linking | ||||
|     libs_to_link = link_ctx.dynamic_libraries_for_runtime.to_list() | ||||
| 
 | ||||
|     # External C libraries that we need to make available to the REPL. | ||||
|     libraries = link_libraries(libs_to_link, args) | ||||
| 
 | ||||
|     # Transitive library dependencies to have in runfiles. | ||||
|     (library_deps, ld_library_deps, ghc_env) = get_libs_for_ghc_linker( | ||||
|         hs, | ||||
|         hs_info.transitive_cc_dependencies, | ||||
|         path_prefix = "$RULES_HASKELL_EXEC_ROOT", | ||||
|     ) | ||||
|     library_path = [paths.dirname(lib.path) for lib in library_deps] | ||||
|     ld_library_path = [paths.dirname(lib.path) for lib in ld_library_deps] | ||||
| 
 | ||||
|     repl_file = hs.actions.declare_file(target_unique_name(hs, "repl")) | ||||
| 
 | ||||
|     add_sources = ["*" + f.path for f in set.to_list(hs_info.source_files)] | ||||
| 
 | ||||
|     ghci_repl_script = hs.actions.declare_file( | ||||
|         target_unique_name(hs, "ghci-repl-script"), | ||||
|     ) | ||||
|     hs.actions.expand_template( | ||||
|         template = ghci_script, | ||||
|         output = ghci_repl_script, | ||||
|         substitutions = { | ||||
|             "{ADD_SOURCES}": " ".join(add_sources), | ||||
|             "{COMMANDS}": "", | ||||
|         }, | ||||
|     ) | ||||
| 
 | ||||
|     # Extra arguments. | ||||
|     # `compiler flags` is the default set of arguments for the repl, | ||||
|     # augmented by `repl_ghci_args`. | ||||
|     # The ordering is important, first compiler flags (from toolchain | ||||
|     # and local rule), then from `repl_ghci_args`. This way the more | ||||
|     # specific arguments are listed last, and then have more priority in | ||||
|     # GHC. | ||||
|     # Note that most flags for GHCI do have their negative value, so a | ||||
|     # negative flag in `repl_ghci_args` can disable a positive flag set | ||||
|     # in `user_compile_flags`, such as `-XNoOverloadedStrings` will disable | ||||
|     # `-XOverloadedStrings`. | ||||
|     args += hs.toolchain.compiler_flags + user_compile_flags + hs.toolchain.repl_ghci_args + repl_ghci_args | ||||
| 
 | ||||
|     hs.actions.expand_template( | ||||
|         template = ghci_repl_wrapper, | ||||
|         output = repl_file, | ||||
|         substitutions = { | ||||
|             "{ENV}": render_env(ghc_env), | ||||
|             "{TOOL}": hs.tools.ghci.path, | ||||
|             "{ARGS}": " ".join( | ||||
|                 [ | ||||
|                     "-ghci-script", | ||||
|                     paths.join("$RULES_HASKELL_EXEC_ROOT", ghci_repl_script.path), | ||||
|                 ] + [ | ||||
|                     shell.quote(a) | ||||
|                     for a in args | ||||
|                 ], | ||||
|             ), | ||||
|         }, | ||||
|         is_executable = True, | ||||
|     ) | ||||
| 
 | ||||
|     ghc_info = struct( | ||||
|         has_version = pkg_ghc_info.has_version, | ||||
|         library_path = library_path, | ||||
|         ld_library_path = ld_library_path, | ||||
|         packages = pkg_ghc_info.packages, | ||||
|         package_ids = pkg_ghc_info.package_ids, | ||||
|         package_dbs = pkg_ghc_info.package_dbs, | ||||
|         lib_imports = lib_imports, | ||||
|         libraries = libraries, | ||||
|         execs = struct( | ||||
|             ghc = hs.tools.ghc.path, | ||||
|             ghci = hs.tools.ghci.path, | ||||
|             runghc = hs.tools.runghc.path, | ||||
|         ), | ||||
|         flags = struct( | ||||
|             compiler = user_compile_flags, | ||||
|             toolchain_compiler = hs.toolchain.compiler_flags, | ||||
|             repl = repl_ghci_args, | ||||
|             toolchain_repl = hs.toolchain.repl_ghci_args, | ||||
|         ), | ||||
|     ) | ||||
|     ghc_info_file = hs.actions.declare_file( | ||||
|         target_unique_name(hs, "ghc-info"), | ||||
|     ) | ||||
|     hs.actions.write( | ||||
|         output = ghc_info_file, | ||||
|         content = ghc_info.to_json(), | ||||
|     ) | ||||
| 
 | ||||
|     # XXX We create a symlink here because we need to force | ||||
|     # hs.tools.ghci and ghci_script and the best way to do that is | ||||
|     # to use hs.actions.run. That action, in turn must produce | ||||
|     # a result, so using ln seems to be the only sane choice. | ||||
|     extra_inputs = depset(transitive = [ | ||||
|         depset([ | ||||
|             hs.tools.ghci, | ||||
|             ghci_repl_script, | ||||
|             repl_file, | ||||
|             ghc_info_file, | ||||
|         ]), | ||||
|         set.to_depset(package_databases), | ||||
|         depset(library_deps), | ||||
|         depset(ld_library_deps), | ||||
|         set.to_depset(hs_info.source_files), | ||||
|     ]) | ||||
|     ln(hs, repl_file, output, extra_inputs) | ||||
							
								
								
									
										115
									
								
								third_party/bazel/rules_haskell/haskell/private/actions/runghc.bzl
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										115
									
								
								third_party/bazel/rules_haskell/haskell/private/actions/runghc.bzl
									
										
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,115 @@ | |||
| """runghc support""" | ||||
| 
 | ||||
| load(":private/context.bzl", "render_env") | ||||
| load(":private/packages.bzl", "expose_packages", "pkg_info_to_compile_flags") | ||||
| load( | ||||
|     ":private/path_utils.bzl", | ||||
|     "get_lib_name", | ||||
|     "is_shared_library", | ||||
|     "link_libraries", | ||||
|     "ln", | ||||
|     "target_unique_name", | ||||
| ) | ||||
| load( | ||||
|     ":private/set.bzl", | ||||
|     "set", | ||||
| ) | ||||
| load(":providers.bzl", "get_libs_for_ghc_linker") | ||||
| load("@bazel_skylib//lib:shell.bzl", "shell") | ||||
| 
 | ||||
| def build_haskell_runghc( | ||||
|         hs, | ||||
|         runghc_wrapper, | ||||
|         user_compile_flags, | ||||
|         extra_args, | ||||
|         hs_info, | ||||
|         output, | ||||
|         package_databases, | ||||
|         version, | ||||
|         lib_info = None): | ||||
|     """Build runghc script. | ||||
| 
 | ||||
|     Args: | ||||
|       hs: Haskell context. | ||||
|       hs_info: HaskellInfo. | ||||
| 
 | ||||
|       package_databases: package caches excluding the cache file of the package | ||||
|                       we're creating a runghc for. | ||||
|       lib_info: If we're building runghc for a library target, pass | ||||
|                 HaskellLibraryInfo here, otherwise it should be None. | ||||
| 
 | ||||
|     Returns: | ||||
|       None. | ||||
|     """ | ||||
| 
 | ||||
|     args = pkg_info_to_compile_flags(expose_packages( | ||||
|         hs_info, | ||||
|         lib_info, | ||||
|         use_direct = False, | ||||
|         use_my_pkg_id = None, | ||||
|         custom_package_databases = package_databases, | ||||
|         version = version, | ||||
|     )) | ||||
| 
 | ||||
|     if lib_info != None: | ||||
|         for idir in set.to_list(hs_info.import_dirs): | ||||
|             args += ["-i{0}".format(idir)] | ||||
| 
 | ||||
|     link_ctx = hs_info.cc_dependencies.dynamic_linking | ||||
|     libs_to_link = link_ctx.dynamic_libraries_for_runtime.to_list() | ||||
| 
 | ||||
|     # External C libraries that we need to make available to runghc. | ||||
|     link_libraries(libs_to_link, args) | ||||
| 
 | ||||
|     # Transitive library dependencies to have in runfiles. | ||||
|     (library_deps, ld_library_deps, ghc_env) = get_libs_for_ghc_linker( | ||||
|         hs, | ||||
|         hs_info.transitive_cc_dependencies, | ||||
|         path_prefix = "$RULES_HASKELL_EXEC_ROOT", | ||||
|     ) | ||||
| 
 | ||||
|     runghc_file = hs.actions.declare_file(target_unique_name(hs, "runghc")) | ||||
| 
 | ||||
|     # Extra arguments. | ||||
|     # `compiler flags` is the default set of arguments for runghc, | ||||
|     # augmented by `extra_args`. | ||||
|     # The ordering is important, first compiler flags (from toolchain | ||||
|     # and local rule), then from `extra_args`. This way the more | ||||
|     # specific arguments are listed last, and then have more priority in | ||||
|     # GHC. | ||||
|     # Note that most flags for GHCI do have their negative value, so a | ||||
|     # negative flag in `extra_args` can disable a positive flag set | ||||
|     # in `user_compile_flags`, such as `-XNoOverloadedStrings` will disable | ||||
|     # `-XOverloadedStrings`. | ||||
|     args += hs.toolchain.compiler_flags + user_compile_flags + hs.toolchain.repl_ghci_args | ||||
| 
 | ||||
|     # ghc args need to be wrapped up in "--ghc-arg=" when passing to runghc | ||||
|     runcompile_flags = ["--ghc-arg=%s" % a for a in args] | ||||
|     runcompile_flags += extra_args | ||||
| 
 | ||||
|     hs.actions.expand_template( | ||||
|         template = runghc_wrapper, | ||||
|         output = runghc_file, | ||||
|         substitutions = { | ||||
|             "{ENV}": render_env(ghc_env), | ||||
|             "{TOOL}": hs.tools.runghc.path, | ||||
|             "{ARGS}": " ".join([shell.quote(a) for a in runcompile_flags]), | ||||
|         }, | ||||
|         is_executable = True, | ||||
|     ) | ||||
| 
 | ||||
|     # XXX We create a symlink here because we need to force | ||||
|     # hs.tools.runghc and the best way to do that is | ||||
|     # to use hs.actions.run. That action, in turn must produce | ||||
|     # a result, so using ln seems to be the only sane choice. | ||||
|     extra_inputs = depset(transitive = [ | ||||
|         depset([ | ||||
|             hs.tools.runghc, | ||||
|             runghc_file, | ||||
|         ]), | ||||
|         set.to_depset(package_databases), | ||||
|         depset(library_deps), | ||||
|         depset(ld_library_deps), | ||||
|         set.to_depset(hs_info.source_files), | ||||
|     ]) | ||||
|     ln(hs, runghc_file, output, extra_inputs) | ||||
							
								
								
									
										64
									
								
								third_party/bazel/rules_haskell/haskell/private/context.bzl
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								third_party/bazel/rules_haskell/haskell/private/context.bzl
									
										
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,64 @@ | |||
| """Derived context with Haskell-specific fields and methods""" | ||||
| 
 | ||||
| load("@bazel_skylib//lib:paths.bzl", "paths") | ||||
| 
 | ||||
| HaskellContext = provider() | ||||
| 
 | ||||
| def haskell_context(ctx, attr = None): | ||||
|     toolchain = ctx.toolchains["@io_tweag_rules_haskell//haskell:toolchain"] | ||||
| 
 | ||||
|     if not attr: | ||||
|         attr = ctx.attr | ||||
| 
 | ||||
|     if hasattr(attr, "src_strip_prefix"): | ||||
|         src_strip_prefix = attr.src_strip_prefix | ||||
|     else: | ||||
|         src_strip_prefix = "" | ||||
| 
 | ||||
|     src_root = paths.join( | ||||
|         ctx.label.workspace_root, | ||||
|         ctx.label.package, | ||||
|         src_strip_prefix, | ||||
|     ) | ||||
| 
 | ||||
|     env = { | ||||
|         "LANG": toolchain.locale, | ||||
|     } | ||||
| 
 | ||||
|     if toolchain.locale_archive != None: | ||||
|         env["LOCALE_ARCHIVE"] = toolchain.locale_archive.path | ||||
| 
 | ||||
|     coverage_enabled = False | ||||
|     if hasattr(ctx, "configuration"): | ||||
|         coverage_enabled = ctx.configuration.coverage_enabled | ||||
| 
 | ||||
|     return HaskellContext( | ||||
|         # Fields | ||||
|         name = attr.name, | ||||
|         label = ctx.label, | ||||
|         toolchain = toolchain, | ||||
|         tools = toolchain.tools, | ||||
|         src_root = src_root, | ||||
|         package_root = ctx.label.workspace_root + ctx.label.package, | ||||
|         env = env, | ||||
|         mode = ctx.var["COMPILATION_MODE"], | ||||
|         actions = ctx.actions, | ||||
|         bin_dir = ctx.bin_dir, | ||||
|         genfiles_dir = ctx.genfiles_dir, | ||||
|         coverage_enabled = coverage_enabled, | ||||
|     ) | ||||
| 
 | ||||
| def render_env(env): | ||||
|     """Render environment dict to shell exports. | ||||
| 
 | ||||
|     Example: | ||||
| 
 | ||||
|       >>> render_env({"PATH": "foo:bar", "LANG": "lang"}) | ||||
|       export PATH=foo:bar | ||||
|       export LANG=lang | ||||
| 
 | ||||
|     """ | ||||
|     return "\n".join([ | ||||
|         "export {}={}".format(k, v) | ||||
|         for k, v in env.items() | ||||
|     ]) | ||||
							
								
								
									
										128
									
								
								third_party/bazel/rules_haskell/haskell/private/coverage_wrapper.sh.tpl
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										128
									
								
								third_party/bazel/rules_haskell/haskell/private/coverage_wrapper.sh.tpl
									
										
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,128 @@ | |||
| #!/usr/bin/env bash | ||||
| # A wrapper for Haskell binaries which have been instrumented for hpc code coverage. | ||||
| 
 | ||||
| # Copy-pasted from Bazel's Bash runfiles library (tools/bash/runfiles/runfiles.bash). | ||||
| set -euo pipefail | ||||
| if [[ ! -d "${RUNFILES_DIR:-/dev/null}" && ! -f "${RUNFILES_MANIFEST_FILE:-/dev/null}" ]]; then | ||||
|   if [[ -f "$0.runfiles_manifest" ]]; then | ||||
|     export RUNFILES_MANIFEST_FILE="$0.runfiles_manifest" | ||||
|   elif [[ -f "$0.runfiles/MANIFEST" ]]; then | ||||
|     export RUNFILES_MANIFEST_FILE="$0.runfiles/MANIFEST" | ||||
|   elif [[ -f "$0.runfiles/bazel_tools/tools/bash/runfiles/runfiles.bash" ]]; then | ||||
|     export RUNFILES_DIR="$0.runfiles" | ||||
|   fi | ||||
| fi | ||||
| if [[ -f "${RUNFILES_DIR:-/dev/null}/bazel_tools/tools/bash/runfiles/runfiles.bash" ]]; then | ||||
|   source "${RUNFILES_DIR}/bazel_tools/tools/bash/runfiles/runfiles.bash" | ||||
| elif [[ -f "${RUNFILES_MANIFEST_FILE:-/dev/null}" ]]; then | ||||
|   source "$(grep -m1 "^bazel_tools/tools/bash/runfiles/runfiles.bash " \ | ||||
|             "$RUNFILES_MANIFEST_FILE" | cut -d ' ' -f 2-)" | ||||
| else | ||||
|   echo >&2 "ERROR: cannot find @bazel_tools//tools/bash/runfiles:runfiles.bash" | ||||
|   exit 1 | ||||
| fi | ||||
| # --- end runfiles.bash initialization --- | ||||
| 
 | ||||
| ERRORCOLOR='\033[1;31m' | ||||
| CLEARCOLOR='\033[0m' | ||||
| binary_path=$(rlocation {binary_path}) | ||||
| hpc_path=$(rlocation {hpc_path}) | ||||
| tix_file_path={tix_file_path} | ||||
| coverage_report_format={coverage_report_format} | ||||
| strict_coverage_analysis={strict_coverage_analysis} | ||||
| package_path={package_path} | ||||
| 
 | ||||
| # either of the two expected coverage metrics should be set to -1 if they're meant to be unused | ||||
| expected_covered_expressions_percentage={expected_covered_expressions_percentage} | ||||
| expected_uncovered_expression_count={expected_uncovered_expression_count} | ||||
| 
 | ||||
| # gather the hpc directories | ||||
| hpc_dir_args="" | ||||
| mix_file_paths={mix_file_paths} | ||||
| for m in "${mix_file_paths[@]}" | ||||
| do | ||||
|   absolute_mix_file_path=$(rlocation $m) | ||||
|   hpc_parent_dir=$(dirname $absolute_mix_file_path) | ||||
|   trimmed_hpc_parent_dir=$(echo "${hpc_parent_dir%%.hpc*}") | ||||
|   hpc_dir_args="$hpc_dir_args --hpcdir=$trimmed_hpc_parent_dir.hpc" | ||||
| done | ||||
| 
 | ||||
| # gather the modules to exclude from the coverage analysis | ||||
| hpc_exclude_args="" | ||||
| modules_to_exclude={modules_to_exclude} | ||||
| for m in "${modules_to_exclude[@]}" | ||||
| do | ||||
|   hpc_exclude_args="$hpc_exclude_args --exclude=$m" | ||||
| done | ||||
| 
 | ||||
| # run the test binary, and then generate the report | ||||
| $binary_path "$@" > /dev/null 2>&1 | ||||
| $hpc_path report "$tix_file_path" $hpc_dir_args $hpc_exclude_args \ | ||||
|   --srcdir "." --srcdir "$package_path" > __hpc_coverage_report | ||||
| 
 | ||||
| # if we want a text report, just output the file generated in the previous step | ||||
| if [ "$coverage_report_format" == "text" ] | ||||
| then | ||||
|   echo "Overall report" | ||||
|   cat __hpc_coverage_report | ||||
| fi | ||||
| 
 | ||||
| # check the covered expression percentage, and if it matches our expectations | ||||
| if [ "$expected_covered_expressions_percentage" -ne -1 ] | ||||
| then | ||||
|   covered_expression_percentage=$(grep "expressions used" __hpc_coverage_report | cut -c 1-3) | ||||
|   if [ "$covered_expression_percentage" -lt "$expected_covered_expressions_percentage" ] | ||||
|   then | ||||
|     echo -e "\n==>$ERRORCOLOR Inadequate expression coverage percentage.$CLEARCOLOR" | ||||
|     echo -e "==> Expected $expected_covered_expressions_percentage%, but the actual coverage was $ERRORCOLOR$(($covered_expression_percentage))%$CLEARCOLOR.\n" | ||||
|     exit 1 | ||||
|   elif [ "$strict_coverage_analysis" == "True" ] && [ "$covered_expression_percentage" -gt "$expected_covered_expressions_percentage" ] | ||||
|   then | ||||
|     echo -e "\n==>$ERRORCOLOR ** BECAUSE STRICT COVERAGE ANALYSIS IS ENABLED **$CLEARCOLOR" | ||||
|     echo -e "==> Your coverage percentage is now higher than expected.$CLEARCOLOR" | ||||
|     echo -e "==> Expected $expected_covered_expressions_percentage% of expressions covered, but the actual value is $ERRORCOLOR$(($covered_expression_percentage))%$CLEARCOLOR." | ||||
|     echo -e "==> Please increase the expected coverage percentage to match.\n" | ||||
|     exit 1 | ||||
|   fi | ||||
| fi | ||||
| 
 | ||||
| # check how many uncovered expressions there are, and if that number matches our expectations | ||||
| if [ "$expected_uncovered_expression_count" -ne -1 ] | ||||
| then | ||||
|   coverage_numerator=$(grep "expressions used" __hpc_coverage_report | sed s:.*\(::g | cut -f1 -d "/") | ||||
|   coverage_denominator=$(grep "expressions used" __hpc_coverage_report | sed s:.*/::g | cut -f1 -d ")") | ||||
|   uncovered_expression_count="$(($coverage_denominator - $coverage_numerator))" | ||||
|   if [ "$uncovered_expression_count" -gt "$expected_uncovered_expression_count" ] | ||||
|   then | ||||
|     echo -e "\n==>$ERRORCOLOR Too many uncovered expressions.$CLEARCOLOR" | ||||
|     echo -e "==> Expected $expected_uncovered_expression_count uncovered expressions, but the actual count was $ERRORCOLOR$(($uncovered_expression_count))$CLEARCOLOR.\n" | ||||
|     exit 1 | ||||
|   elif [ "$strict_coverage_analysis" == "True" ] && [ "$uncovered_expression_count" -lt "$expected_uncovered_expression_count" ] | ||||
|   then | ||||
|     echo -e "\n==>$ERRORCOLOR ** BECAUSE STRICT COVERAGE ANALYSIS IS ENABLED **$CLEARCOLOR" | ||||
|     echo -e "==>$ERRORCOLOR Your uncovered expression count is now lower than expected.$CLEARCOLOR" | ||||
|     echo -e "==> Expected $expected_uncovered_expression_count uncovered expressions, but there is $ERRORCOLOR$(($uncovered_expression_count))$CLEARCOLOR." | ||||
|     echo -e "==> Please lower the expected uncovered expression count to match.\n" | ||||
|     exit 1 | ||||
|   fi | ||||
| fi | ||||
| 
 | ||||
| # if we want an html report, run the hpc binary again with the "markup" command, | ||||
| # and feed its generated files into stdout, wrapped in XML tags | ||||
| if [ "$coverage_report_format" == "html" ] | ||||
| then | ||||
|   $hpc_path markup "$tix_file_path" $hpc_dir_args $hpc_exclude_args \ | ||||
|     --srcdir "." --srcdir "$package_path" --destdir=hpc_out > /dev/null 2>&1 | ||||
|   cd hpc_out | ||||
|   echo "COVERAGE REPORT BELOW" | ||||
|   echo "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%" | ||||
|   for file in *.html **/*.hs.html; do | ||||
|     [ -e "$file" ] || continue | ||||
|     echo "<coverage-report-part name=\"$file\">" | ||||
|     echo '<![CDATA[' | ||||
|     cat $file | ||||
|     echo ']]>' | ||||
|     echo "</coverage-report-part>" | ||||
|   done | ||||
|   echo "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%" | ||||
| fi | ||||
							
								
								
									
										222
									
								
								third_party/bazel/rules_haskell/haskell/private/dependencies.bzl
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										222
									
								
								third_party/bazel/rules_haskell/haskell/private/dependencies.bzl
									
										
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,222 @@ | |||
| load("@bazel_skylib//lib:dicts.bzl", "dicts") | ||||
| load("@bazel_skylib//lib:paths.bzl", "paths") | ||||
| load( | ||||
|     "@io_tweag_rules_haskell//haskell:providers.bzl", | ||||
|     "HaskellCcInfo", | ||||
|     "HaskellInfo", | ||||
|     "HaskellLibraryInfo", | ||||
|     "HaskellPrebuiltPackageInfo", | ||||
|     "empty_HaskellCcInfo", | ||||
|     "merge_HaskellCcInfo", | ||||
| ) | ||||
| load( | ||||
|     ":private/path_utils.bzl", | ||||
|     "get_lib_name", | ||||
|     "is_shared_library", | ||||
|     "is_static_library", | ||||
|     "ln", | ||||
| ) | ||||
| load(":private/set.bzl", "set") | ||||
| 
 | ||||
| def _cc_get_static_lib(lib_info): | ||||
|     """Return the library to use in static linking mode. | ||||
| 
 | ||||
|     This returns the first available library artifact in the following order: | ||||
|     - static_library | ||||
|     - pic_static_library | ||||
|     - dynamic_library | ||||
|     - interface_library | ||||
| 
 | ||||
|     Args: | ||||
|       lib_info: LibraryToLink provider. | ||||
| 
 | ||||
|     Returns: | ||||
|       File: The library to link against in static mode. | ||||
|     """ | ||||
|     if lib_info.static_library: | ||||
|         return lib_info.static_library | ||||
|     elif lib_info.pic_static_library: | ||||
|         return lib_info.pic_static_library | ||||
|     elif lib_info.dynamic_library: | ||||
|         return lib_info.dynamic_library | ||||
|     else: | ||||
|         return lib_info.interface_library | ||||
| 
 | ||||
| def _cc_get_dynamic_lib(lib_info): | ||||
|     """Return the library to use in dynamic linking mode. | ||||
| 
 | ||||
|     This returns the first available library artifact in the following order: | ||||
|     - dynamic_library | ||||
|     - interface_library | ||||
|     - pic_static_library | ||||
|     - static_library | ||||
| 
 | ||||
|     Args: | ||||
|       lib_info: LibraryToLink provider. | ||||
| 
 | ||||
|     Returns: | ||||
|       File: The library to link against in dynamic mode. | ||||
|     """ | ||||
|     if lib_info.dynamic_library: | ||||
|         return lib_info.dynamic_library | ||||
|     elif lib_info.interface_library: | ||||
|         return lib_info.interface_library | ||||
|     elif lib_info.pic_static_library: | ||||
|         return lib_info.pic_static_library | ||||
|     else: | ||||
|         return lib_info.static_library | ||||
| 
 | ||||
| def _HaskellCcInfo_from_CcInfo(ctx, cc_info): | ||||
|     libs_to_link = cc_info.linking_context.libraries_to_link | ||||
|     static_libs_to_link = [] | ||||
|     dynamic_libs_to_link = [] | ||||
|     static_libs_for_runtime = [] | ||||
|     dynamic_libs_for_runtime = [] | ||||
|     for l in libs_to_link: | ||||
|         _static_lib = _cc_get_static_lib(l) | ||||
|         dynamic_lib = _cc_get_dynamic_lib(l) | ||||
| 
 | ||||
|         # Bazel itself only mangles dynamic libraries, not static libraries. | ||||
|         # However, we need the library name of the static and dynamic version | ||||
|         # of a library to match so that we can refer to both with one entry in | ||||
|         # the package configuration file. Here we rename any static archives | ||||
|         # with mismatching mangled dynamic library name. | ||||
|         static_name = get_lib_name(_static_lib) | ||||
|         dynamic_name = get_lib_name(dynamic_lib) | ||||
|         if static_name != dynamic_name: | ||||
|             ext = _static_lib.extension | ||||
|             static_lib = ctx.actions.declare_file( | ||||
|                 "lib%s.%s" % (dynamic_name, ext), | ||||
|             ) | ||||
|             ln(ctx, _static_lib, static_lib) | ||||
|         else: | ||||
|             static_lib = _static_lib | ||||
| 
 | ||||
|         static_libs_to_link.append(static_lib) | ||||
|         if is_shared_library(static_lib): | ||||
|             static_libs_for_runtime.append(static_lib) | ||||
|         dynamic_libs_to_link.append(dynamic_lib) | ||||
|         if is_shared_library(dynamic_lib): | ||||
|             dynamic_libs_for_runtime.append(dynamic_lib) | ||||
| 
 | ||||
|     return HaskellCcInfo( | ||||
|         static_linking = struct( | ||||
|             libraries_to_link = depset( | ||||
|                 direct = static_libs_to_link, | ||||
|                 order = "topological", | ||||
|             ), | ||||
|             dynamic_libraries_for_runtime = depset( | ||||
|                 direct = static_libs_for_runtime, | ||||
|                 order = "topological", | ||||
|             ), | ||||
|             user_link_flags = depset( | ||||
|                 direct = cc_info.linking_context.user_link_flags, | ||||
|                 order = "topological", | ||||
|             ), | ||||
|         ), | ||||
|         dynamic_linking = struct( | ||||
|             libraries_to_link = depset( | ||||
|                 direct = dynamic_libs_to_link, | ||||
|                 order = "topological", | ||||
|             ), | ||||
|             dynamic_libraries_for_runtime = depset( | ||||
|                 direct = dynamic_libs_for_runtime, | ||||
|                 order = "topological", | ||||
|             ), | ||||
|             user_link_flags = depset( | ||||
|                 direct = cc_info.linking_context.user_link_flags, | ||||
|                 order = "topological", | ||||
|             ), | ||||
|         ), | ||||
|     ) | ||||
| 
 | ||||
| def gather_dep_info(ctx, deps): | ||||
|     """Collapse dependencies into a single `HaskellInfo`. | ||||
| 
 | ||||
|     Note that the field `prebuilt_dependencies` also includes | ||||
|     prebuilt_dependencies of current target. | ||||
| 
 | ||||
|     Args: | ||||
|       ctx: Rule context. | ||||
|       deps: deps attribute. | ||||
| 
 | ||||
|     Returns: | ||||
|       HaskellInfo: Unified information about all dependencies. | ||||
|     """ | ||||
| 
 | ||||
|     acc = HaskellInfo( | ||||
|         package_ids = set.empty(), | ||||
|         package_databases = set.empty(), | ||||
|         version_macros = set.empty(), | ||||
|         static_libraries = [], | ||||
|         static_libraries_prof = [], | ||||
|         dynamic_libraries = set.empty(), | ||||
|         interface_dirs = set.empty(), | ||||
|         prebuilt_dependencies = set.empty(), | ||||
|         direct_prebuilt_deps = set.empty(), | ||||
|         cc_dependencies = empty_HaskellCcInfo(), | ||||
|         transitive_cc_dependencies = empty_HaskellCcInfo(), | ||||
|     ) | ||||
| 
 | ||||
|     for dep in deps: | ||||
|         if HaskellInfo in dep: | ||||
|             binfo = dep[HaskellInfo] | ||||
|             package_ids = acc.package_ids | ||||
|             if HaskellLibraryInfo not in dep: | ||||
|                 fail("Target {0} cannot depend on binary".format(ctx.attr.name)) | ||||
|             if HaskellLibraryInfo in dep: | ||||
|                 set.mutable_insert(package_ids, dep[HaskellLibraryInfo].package_id) | ||||
|             acc = HaskellInfo( | ||||
|                 package_ids = package_ids, | ||||
|                 package_databases = set.mutable_union(acc.package_databases, binfo.package_databases), | ||||
|                 version_macros = set.mutable_union(acc.version_macros, binfo.version_macros), | ||||
|                 static_libraries = acc.static_libraries + binfo.static_libraries, | ||||
|                 static_libraries_prof = acc.static_libraries_prof + binfo.static_libraries_prof, | ||||
|                 dynamic_libraries = set.mutable_union(acc.dynamic_libraries, binfo.dynamic_libraries), | ||||
|                 interface_dirs = set.mutable_union(acc.interface_dirs, binfo.interface_dirs), | ||||
|                 prebuilt_dependencies = set.mutable_union(acc.prebuilt_dependencies, binfo.prebuilt_dependencies), | ||||
|                 direct_prebuilt_deps = acc.direct_prebuilt_deps, | ||||
|                 cc_dependencies = acc.cc_dependencies, | ||||
|                 transitive_cc_dependencies = merge_HaskellCcInfo(acc.transitive_cc_dependencies, binfo.transitive_cc_dependencies), | ||||
|             ) | ||||
|         elif HaskellPrebuiltPackageInfo in dep: | ||||
|             pkg = dep[HaskellPrebuiltPackageInfo] | ||||
|             acc = HaskellInfo( | ||||
|                 package_ids = acc.package_ids, | ||||
|                 package_databases = acc.package_databases, | ||||
|                 version_macros = set.mutable_insert(acc.version_macros, pkg.version_macros_file), | ||||
|                 static_libraries = acc.static_libraries, | ||||
|                 static_libraries_prof = acc.static_libraries_prof, | ||||
|                 dynamic_libraries = acc.dynamic_libraries, | ||||
|                 interface_dirs = acc.interface_dirs, | ||||
|                 prebuilt_dependencies = set.mutable_insert(acc.prebuilt_dependencies, pkg), | ||||
|                 direct_prebuilt_deps = set.mutable_insert(acc.direct_prebuilt_deps, pkg), | ||||
|                 cc_dependencies = acc.cc_dependencies, | ||||
|                 transitive_cc_dependencies = acc.transitive_cc_dependencies, | ||||
|             ) | ||||
|         elif CcInfo in dep and HaskellInfo not in dep: | ||||
|             # The final link of a binary must include all static libraries we | ||||
|             # depend on, including transitives ones. Theses libs are provided | ||||
|             # in the `CcInfo` provider. | ||||
|             hs_cc_info = _HaskellCcInfo_from_CcInfo(ctx, dep[CcInfo]) | ||||
|             acc = HaskellInfo( | ||||
|                 package_ids = acc.package_ids, | ||||
|                 package_databases = acc.package_databases, | ||||
|                 version_macros = acc.version_macros, | ||||
|                 static_libraries = acc.static_libraries, | ||||
|                 static_libraries_prof = acc.static_libraries_prof, | ||||
|                 dynamic_libraries = acc.dynamic_libraries, | ||||
|                 interface_dirs = acc.interface_dirs, | ||||
|                 prebuilt_dependencies = acc.prebuilt_dependencies, | ||||
|                 direct_prebuilt_deps = acc.direct_prebuilt_deps, | ||||
|                 cc_dependencies = merge_HaskellCcInfo( | ||||
|                     acc.cc_dependencies, | ||||
|                     hs_cc_info, | ||||
|                 ), | ||||
|                 transitive_cc_dependencies = merge_HaskellCcInfo( | ||||
|                     acc.transitive_cc_dependencies, | ||||
|                     hs_cc_info, | ||||
|                 ), | ||||
|             ) | ||||
| 
 | ||||
|     return acc | ||||
							
								
								
									
										59
									
								
								third_party/bazel/rules_haskell/haskell/private/ghci_repl_wrapper.sh
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								third_party/bazel/rules_haskell/haskell/private/ghci_repl_wrapper.sh
									
										
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,59 @@ | |||
| #!/usr/bin/env bash | ||||
| # | ||||
| # Usage: ghci_repl_wrapper.sh <ARGS> | ||||
| 
 | ||||
| # this variable is set by `bazel run` | ||||
| if [ "$BUILD_WORKSPACE_DIRECTORY" = "" ] | ||||
| then | ||||
|     cat <<EOF | ||||
| It looks like you are trying to invoke the REPL incorrectly. | ||||
| We only support calling the repl script with | ||||
| 
 | ||||
| $ bazel run <target> | ||||
| 
 | ||||
| for now. | ||||
| 
 | ||||
| If you are on bazel < 0.15 you must invoke as follows: | ||||
| 
 | ||||
| $ bazel run --direct_run <target> | ||||
| EOF | ||||
|     exit 1 | ||||
| fi | ||||
| 
 | ||||
| # Derived from Bazel's Bash runfiles library (tools/bash/runfiles/runfiles.bash). | ||||
| if [[ -z "$RUNFILES_DIR" ]]; then | ||||
|   if [[ -d "$0.runfiles" ]]; then | ||||
|     export RUNFILES_DIR="$0.runfiles" | ||||
|   fi | ||||
| fi | ||||
| if [[ -z "$RUNFILES_MANIFEST_FILE" ]]; then | ||||
|   if [[ -f "$0.runfiles_manifest" ]]; then | ||||
|     export RUNFILES_MANIFEST_FILE="$0.runfiles_manifest" | ||||
|   elif [[ -f "$0.runfiles/MANIFEST" ]]; then | ||||
|     export RUNFILES_MANIFEST_FILE="$0.runfiles/MANIFEST" | ||||
|   fi | ||||
| fi | ||||
| 
 | ||||
| # GHCi script and libraries are loaded relative to workspace directory. | ||||
| # bazel run //some:target@repl will be executed from the workspace directory. | ||||
| # bazel run //some:haskell_repl will be executed from its execroot. | ||||
| # Explicitly change into the workspace root in that case. | ||||
| cd "$BUILD_WORKSPACE_DIRECTORY" | ||||
| 
 | ||||
| # This is a workaround for https://github.com/bazelbuild/bazel/issues/5506 | ||||
| # and also for the fact that REPL script relies on so-called “convenience | ||||
| # links” and the names of those links are controlled by the --symlink_prefix | ||||
| # option, which can be set by the user to something unpredictable. | ||||
| # | ||||
| # It seems that we can't locate the files of interest/build outputs in | ||||
| # general. However, due to “internal issues” in Bazel mentioned e.g. | ||||
| # https://github.com/bazelbuild/bazel/issues/3796, the directory bazel-out | ||||
| # is always created under the workspace directory. We exploit this to get | ||||
| # location of exec root reliably and then prefix locations of various | ||||
| # components, such as shared libraries with that exec root. | ||||
| 
 | ||||
| RULES_HASKELL_EXEC_ROOT=$(dirname $(readlink ${BUILD_WORKSPACE_DIRECTORY}/bazel-out)) | ||||
| TOOL_LOCATION="$RULES_HASKELL_EXEC_ROOT/{TOOL}" | ||||
| 
 | ||||
| {ENV} | ||||
| "$TOOL_LOCATION" {ARGS} "$@" | ||||
							
								
								
									
										49
									
								
								third_party/bazel/rules_haskell/haskell/private/haddock_wrapper.sh.tpl
									
										
									
									
										vendored
									
									
										Executable file
									
								
							
							
						
						
									
										49
									
								
								third_party/bazel/rules_haskell/haskell/private/haddock_wrapper.sh.tpl
									
										
									
									
										vendored
									
									
										Executable file
									
								
							|  | @ -0,0 +1,49 @@ | |||
| #!/usr/bin/env bash | ||||
| # | ||||
| # Usage: haddock-wrapper.sh <PREBUILD_DEPS_FILE> <HADDOCK_ARGS> | ||||
| 
 | ||||
| set -eo pipefail | ||||
| 
 | ||||
| %{env} | ||||
| 
 | ||||
| PREBUILT_DEPS_FILE=$1 | ||||
| shift | ||||
| 
 | ||||
| extra_args=() | ||||
| 
 | ||||
| for pkg in $(< $PREBUILT_DEPS_FILE) | ||||
| do | ||||
|     # Assumption: the `haddock-interfaces` field always only contains | ||||
|     # exactly one file name. This seems to hold in practice, though the | ||||
|     # ghc documentation defines it as: | ||||
|     # > (string list) A list of filenames containing Haddock interface files | ||||
|     # > (.haddock files) for this package. | ||||
|     # 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) | ||||
| 
 | ||||
|     # Sometimes the referenced `.haddock` file does not exist | ||||
|     # (e.g. for `nixpkgs.haskellPackages` deps with haddock disabled). | ||||
|     # In that case, skip this package with a warning. | ||||
|     if [[ -f "$haddock_interfaces" ]] | ||||
|     then | ||||
|         # TODO: link source code, | ||||
|         # `--read-interface=$haddock_html,$pkg_src,$haddock_interfaces | ||||
|         # https://haskell-haddock.readthedocs.io/en/latest/invoking.html#cmdoption-read-interface | ||||
|         extra_args+=("--read-interface=$haddock_html,$haddock_interfaces") | ||||
|     else | ||||
|         echo "Warning: haddock missing for package $pkg" 1>&2 | ||||
|     fi | ||||
| done | ||||
| 
 | ||||
| # BSD and GNU mktemp are very different; attempt GNU first | ||||
| TEMP=$(mktemp -d 2>/dev/null || mktemp -d -t 'haddock_wrapper') | ||||
| trap cleanup 1 2 3 6 | ||||
| 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[@]}" "$@" | ||||
| cleanup | ||||
							
								
								
									
										668
									
								
								third_party/bazel/rules_haskell/haskell/private/haskell_impl.bzl
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										668
									
								
								third_party/bazel/rules_haskell/haskell/private/haskell_impl.bzl
									
										
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,668 @@ | |||
| """Implementation of core Haskell rules""" | ||||
| 
 | ||||
| load( | ||||
|     "@io_tweag_rules_haskell//haskell:providers.bzl", | ||||
|     "C2hsLibraryInfo", | ||||
|     "HaskellInfo", | ||||
|     "HaskellLibraryInfo", | ||||
|     "HaskellPrebuiltPackageInfo", | ||||
| ) | ||||
| load(":cc.bzl", "cc_interop_info") | ||||
| load( | ||||
|     ":private/actions/link.bzl", | ||||
|     "link_binary", | ||||
|     "link_library_dynamic", | ||||
|     "link_library_static", | ||||
| ) | ||||
| load(":private/actions/package.bzl", "package") | ||||
| load(":private/actions/repl.bzl", "build_haskell_repl") | ||||
| load(":private/actions/runghc.bzl", "build_haskell_runghc") | ||||
| load(":private/context.bzl", "haskell_context") | ||||
| load(":private/dependencies.bzl", "gather_dep_info") | ||||
| load(":private/java.bzl", "java_interop_info") | ||||
| load(":private/mode.bzl", "is_profiling_enabled") | ||||
| load( | ||||
|     ":private/path_utils.bzl", | ||||
|     "ln", | ||||
|     "match_label", | ||||
|     "parse_pattern", | ||||
|     "target_unique_name", | ||||
| ) | ||||
| load(":private/pkg_id.bzl", "pkg_id") | ||||
| load(":private/set.bzl", "set") | ||||
| load(":private/version_macros.bzl", "generate_version_macros") | ||||
| load(":providers.bzl", "GhcPluginInfo", "HaskellCoverageInfo") | ||||
| load("@bazel_skylib//lib:paths.bzl", "paths") | ||||
| load("@bazel_skylib//lib:collections.bzl", "collections") | ||||
| load("@bazel_skylib//lib:shell.bzl", "shell") | ||||
| 
 | ||||
| def _prepare_srcs(srcs): | ||||
|     srcs_files = [] | ||||
|     import_dir_map = {} | ||||
| 
 | ||||
|     for src in srcs: | ||||
|         # If it has the "files" attribute, it must be a Target | ||||
|         if hasattr(src, "files"): | ||||
|             if C2hsLibraryInfo in src: | ||||
|                 srcs_files += src.files.to_list() | ||||
|                 for f in src.files.to_list(): | ||||
|                     import_dir_map[f] = src[C2hsLibraryInfo].import_dir | ||||
|             else: | ||||
|                 srcs_files += src.files.to_list() | ||||
| 
 | ||||
|             # otherwise it's just a file | ||||
| 
 | ||||
|         else: | ||||
|             srcs_files.append(src) | ||||
| 
 | ||||
|     return srcs_files, import_dir_map | ||||
| 
 | ||||
| def haskell_test_impl(ctx): | ||||
|     return _haskell_binary_common_impl(ctx, is_test = True) | ||||
| 
 | ||||
| def haskell_binary_impl(ctx): | ||||
|     return _haskell_binary_common_impl(ctx, is_test = False) | ||||
| 
 | ||||
| def _should_inspect_coverage(ctx, hs, is_test): | ||||
|     return hs.coverage_enabled and is_test | ||||
| 
 | ||||
| def _coverage_enabled_for_target(coverage_source_patterns, label): | ||||
|     for pat in coverage_source_patterns: | ||||
|         if match_label(pat, label): | ||||
|             return True | ||||
| 
 | ||||
|     return False | ||||
| 
 | ||||
| # Mix files refer to genfile srcs including their root. Therefore, we | ||||
| # must condition the src filepaths passed in for coverage to match. | ||||
| def _condition_coverage_src(hs, src): | ||||
|     if not src.path.startswith(hs.genfiles_dir.path): | ||||
|         return src | ||||
| 
 | ||||
|     """ Genfiles have the genfile directory as part of their path, | ||||
|     so declaring a file with the sample path actually makes the new | ||||
|     file double-qualified by the genfile directory. | ||||
| 
 | ||||
|     This is necessary because mix files capture the genfile | ||||
|     path before compilation, and then expect those files to be | ||||
|     qualified by the genfile directory when `hpc report` or | ||||
|     `hpc markup` are used. But, genfiles included as runfiles | ||||
|     are no longer qualified. So, double-qualifying them results in | ||||
|     only one level of qualification as runfiles. | ||||
|     """ | ||||
|     conditioned_src = hs.actions.declare_file(src.path) | ||||
|     hs.actions.run_shell( | ||||
|         inputs = [src], | ||||
|         outputs = [conditioned_src], | ||||
|         arguments = [ | ||||
|             src.path, | ||||
|             conditioned_src.path, | ||||
|         ], | ||||
|         command = """ | ||||
|         mkdir -p $(dirname "$2") && cp "$1" "$2" | ||||
|         """, | ||||
|     ) | ||||
| 
 | ||||
|     return conditioned_src | ||||
| 
 | ||||
| def _haskell_binary_common_impl(ctx, is_test): | ||||
|     hs = haskell_context(ctx) | ||||
|     dep_info = gather_dep_info(ctx, ctx.attr.deps) | ||||
|     plugin_dep_info = gather_dep_info( | ||||
|         ctx, | ||||
|         [dep for plugin in ctx.attr.plugins for dep in plugin[GhcPluginInfo].deps], | ||||
|     ) | ||||
| 
 | ||||
|     # Add any interop info for other languages. | ||||
|     cc = cc_interop_info(ctx) | ||||
|     java = java_interop_info(ctx) | ||||
| 
 | ||||
|     with_profiling = is_profiling_enabled(hs) | ||||
|     srcs_files, import_dir_map = _prepare_srcs(ctx.attr.srcs) | ||||
|     inspect_coverage = _should_inspect_coverage(ctx, hs, is_test) | ||||
| 
 | ||||
|     c = hs.toolchain.actions.compile_binary( | ||||
|         hs, | ||||
|         cc, | ||||
|         java, | ||||
|         dep_info, | ||||
|         plugin_dep_info, | ||||
|         srcs = srcs_files, | ||||
|         ls_modules = ctx.executable._ls_modules, | ||||
|         import_dir_map = import_dir_map, | ||||
|         extra_srcs = depset(ctx.files.extra_srcs), | ||||
|         user_compile_flags = ctx.attr.compiler_flags, | ||||
|         dynamic = False if hs.toolchain.is_windows else not ctx.attr.linkstatic, | ||||
|         with_profiling = False, | ||||
|         main_function = ctx.attr.main_function, | ||||
|         version = ctx.attr.version, | ||||
|         inspect_coverage = inspect_coverage, | ||||
|         plugins = ctx.attr.plugins, | ||||
|     ) | ||||
| 
 | ||||
|     # gather intermediary code coverage instrumentation data | ||||
|     coverage_data = c.coverage_data | ||||
|     for dep in ctx.attr.deps: | ||||
|         if HaskellCoverageInfo in dep: | ||||
|             coverage_data += dep[HaskellCoverageInfo].coverage_data | ||||
| 
 | ||||
|     c_p = None | ||||
| 
 | ||||
|     if with_profiling: | ||||
|         c_p = hs.toolchain.actions.compile_binary( | ||||
|             hs, | ||||
|             cc, | ||||
|             java, | ||||
|             dep_info, | ||||
|             plugin_dep_info, | ||||
|             srcs = srcs_files, | ||||
|             ls_modules = ctx.executable._ls_modules, | ||||
|             import_dir_map = import_dir_map, | ||||
|             # NOTE We must make the object files compiled without profiling | ||||
|             # available to this step for TH to work, presumably because GHC is | ||||
|             # linked against RTS without profiling. | ||||
|             extra_srcs = depset(transitive = [ | ||||
|                 depset(ctx.files.extra_srcs), | ||||
|                 depset([c.objects_dir]), | ||||
|             ]), | ||||
|             user_compile_flags = ctx.attr.compiler_flags, | ||||
|             # NOTE We can't have profiling and dynamic code at the | ||||
|             # same time, see: | ||||
|             # https://ghc.haskell.org/trac/ghc/ticket/15394 | ||||
|             dynamic = False, | ||||
|             with_profiling = True, | ||||
|             main_function = ctx.attr.main_function, | ||||
|             version = ctx.attr.version, | ||||
|             plugins = ctx.attr.plugins, | ||||
|         ) | ||||
| 
 | ||||
|     (binary, solibs) = link_binary( | ||||
|         hs, | ||||
|         cc, | ||||
|         dep_info, | ||||
|         ctx.files.extra_srcs, | ||||
|         ctx.attr.compiler_flags, | ||||
|         c_p.objects_dir if with_profiling else c.objects_dir, | ||||
|         dynamic = False if hs.toolchain.is_windows else not ctx.attr.linkstatic, | ||||
|         with_profiling = with_profiling, | ||||
|         version = ctx.attr.version, | ||||
|     ) | ||||
| 
 | ||||
|     hs_info = HaskellInfo( | ||||
|         package_ids = dep_info.package_ids, | ||||
|         package_databases = dep_info.package_databases, | ||||
|         version_macros = set.empty(), | ||||
|         source_files = c.source_files, | ||||
|         extra_source_files = c.extra_source_files, | ||||
|         import_dirs = c.import_dirs, | ||||
|         static_libraries = dep_info.static_libraries, | ||||
|         static_libraries_prof = dep_info.static_libraries_prof, | ||||
|         dynamic_libraries = dep_info.dynamic_libraries, | ||||
|         interface_dirs = dep_info.interface_dirs, | ||||
|         compile_flags = c.compile_flags, | ||||
|         prebuilt_dependencies = dep_info.prebuilt_dependencies, | ||||
|         cc_dependencies = dep_info.cc_dependencies, | ||||
|         transitive_cc_dependencies = dep_info.transitive_cc_dependencies, | ||||
|     ) | ||||
|     cc_info = cc_common.merge_cc_infos( | ||||
|         cc_infos = [dep[CcInfo] for dep in ctx.attr.deps if CcInfo in dep], | ||||
|     ) | ||||
| 
 | ||||
|     target_files = depset([binary]) | ||||
| 
 | ||||
|     build_haskell_repl( | ||||
|         hs, | ||||
|         ghci_script = ctx.file._ghci_script, | ||||
|         ghci_repl_wrapper = ctx.file._ghci_repl_wrapper, | ||||
|         user_compile_flags = ctx.attr.compiler_flags, | ||||
|         repl_ghci_args = ctx.attr.repl_ghci_args, | ||||
|         output = ctx.outputs.repl, | ||||
|         package_databases = dep_info.package_databases, | ||||
|         version = ctx.attr.version, | ||||
|         hs_info = hs_info, | ||||
|     ) | ||||
| 
 | ||||
|     # XXX Temporary backwards compatibility hack. Remove eventually. | ||||
|     # See https://github.com/tweag/rules_haskell/pull/460. | ||||
|     ln(hs, ctx.outputs.repl, ctx.outputs.repl_deprecated) | ||||
| 
 | ||||
|     build_haskell_runghc( | ||||
|         hs, | ||||
|         runghc_wrapper = ctx.file._ghci_repl_wrapper, | ||||
|         extra_args = ctx.attr.runcompile_flags, | ||||
|         user_compile_flags = ctx.attr.compiler_flags, | ||||
|         output = ctx.outputs.runghc, | ||||
|         package_databases = dep_info.package_databases, | ||||
|         version = ctx.attr.version, | ||||
|         hs_info = hs_info, | ||||
|     ) | ||||
| 
 | ||||
|     executable = binary | ||||
|     extra_runfiles = [] | ||||
| 
 | ||||
|     if inspect_coverage: | ||||
|         binary_path = paths.join(ctx.workspace_name, binary.short_path) | ||||
|         hpc_path = paths.join(ctx.workspace_name, hs.toolchain.tools.hpc.short_path) | ||||
|         tix_file_path = hs.label.name + ".tix" | ||||
|         mix_file_paths = [ | ||||
|             paths.join(ctx.workspace_name, datum.mix_file.short_path) | ||||
|             for datum in coverage_data | ||||
|         ] | ||||
|         mix_file_paths = collections.uniq(mix_file_paths)  # remove duplicates | ||||
| 
 | ||||
|         # find which modules to exclude from coverage analysis, by using the specified source patterns | ||||
|         raw_coverage_source_patterns = ctx.attr.experimental_coverage_source_patterns | ||||
|         coverage_source_patterns = [parse_pattern(ctx, pat) for pat in raw_coverage_source_patterns] | ||||
|         modules_to_exclude = [paths.split_extension(datum.mix_file.basename)[0] for datum in coverage_data if not _coverage_enabled_for_target(coverage_source_patterns, datum.target_label)] | ||||
|         modules_to_exclude = collections.uniq(modules_to_exclude)  # remove duplicates | ||||
| 
 | ||||
|         expected_covered_expressions_percentage = ctx.attr.expected_covered_expressions_percentage | ||||
|         expected_uncovered_expression_count = ctx.attr.expected_uncovered_expression_count | ||||
|         strict_coverage_analysis = ctx.attr.strict_coverage_analysis | ||||
|         coverage_report_format = ctx.attr.coverage_report_format | ||||
| 
 | ||||
|         if coverage_report_format != "text" and coverage_report_format != "html": | ||||
|             fail("""haskell_test attribute "coverage_report_format" must be one of "text" or "html".""") | ||||
| 
 | ||||
|         wrapper = hs.actions.declare_file("{}_coverage/coverage_wrapper.sh".format(ctx.label.name)) | ||||
|         ctx.actions.expand_template( | ||||
|             template = ctx.file._coverage_wrapper_template, | ||||
|             output = wrapper, | ||||
|             substitutions = { | ||||
|                 "{binary_path}": shell.quote(binary_path), | ||||
|                 "{hpc_path}": shell.quote(hpc_path), | ||||
|                 "{tix_file_path}": shell.quote(tix_file_path), | ||||
|                 "{expected_covered_expressions_percentage}": str(expected_covered_expressions_percentage), | ||||
|                 "{expected_uncovered_expression_count}": str(expected_uncovered_expression_count), | ||||
|                 "{mix_file_paths}": shell.array_literal(mix_file_paths), | ||||
|                 "{modules_to_exclude}": shell.array_literal(modules_to_exclude), | ||||
|                 "{strict_coverage_analysis}": str(strict_coverage_analysis), | ||||
|                 "{coverage_report_format}": shell.quote(ctx.attr.coverage_report_format), | ||||
|                 "{package_path}": shell.quote(ctx.label.package), | ||||
|             }, | ||||
|             is_executable = True, | ||||
|         ) | ||||
|         executable = wrapper | ||||
|         mix_runfiles = [datum.mix_file for datum in coverage_data] | ||||
|         srcs_runfiles = [_condition_coverage_src(hs, datum.src_file) for datum in coverage_data] | ||||
|         extra_runfiles = [ | ||||
|             ctx.file._bash_runfiles, | ||||
|             hs.toolchain.tools.hpc, | ||||
|             binary, | ||||
|         ] + mix_runfiles + srcs_runfiles | ||||
| 
 | ||||
|     return [ | ||||
|         hs_info, | ||||
|         cc_info, | ||||
|         DefaultInfo( | ||||
|             executable = executable, | ||||
|             files = target_files, | ||||
|             runfiles = ctx.runfiles( | ||||
|                 files = | ||||
|                     solibs + | ||||
|                     extra_runfiles, | ||||
|                 collect_data = True, | ||||
|             ), | ||||
|         ), | ||||
|     ] | ||||
| 
 | ||||
| def haskell_library_impl(ctx): | ||||
|     hs = haskell_context(ctx) | ||||
|     dep_info = gather_dep_info(ctx, ctx.attr.deps) | ||||
|     plugin_dep_info = gather_dep_info( | ||||
|         ctx, | ||||
|         [dep for plugin in ctx.attr.plugins for dep in plugin[GhcPluginInfo].deps], | ||||
|     ) | ||||
|     version = ctx.attr.version if ctx.attr.version else None | ||||
|     my_pkg_id = pkg_id.new(ctx.label, version) | ||||
|     with_profiling = is_profiling_enabled(hs) | ||||
|     with_shared = False if hs.toolchain.is_windows else not ctx.attr.linkstatic | ||||
| 
 | ||||
|     # Add any interop info for other languages. | ||||
|     cc = cc_interop_info(ctx) | ||||
|     java = java_interop_info(ctx) | ||||
| 
 | ||||
|     srcs_files, import_dir_map = _prepare_srcs(ctx.attr.srcs) | ||||
|     other_modules = ctx.attr.hidden_modules | ||||
|     exposed_modules_reexports = _exposed_modules_reexports(ctx.attr.exports) | ||||
| 
 | ||||
|     c = hs.toolchain.actions.compile_library( | ||||
|         hs, | ||||
|         cc, | ||||
|         java, | ||||
|         dep_info, | ||||
|         plugin_dep_info, | ||||
|         srcs = srcs_files, | ||||
|         ls_modules = ctx.executable._ls_modules, | ||||
|         other_modules = other_modules, | ||||
|         exposed_modules_reexports = exposed_modules_reexports, | ||||
|         import_dir_map = import_dir_map, | ||||
|         extra_srcs = depset(ctx.files.extra_srcs), | ||||
|         user_compile_flags = ctx.attr.compiler_flags, | ||||
|         with_shared = with_shared, | ||||
|         with_profiling = False, | ||||
|         my_pkg_id = my_pkg_id, | ||||
|         plugins = ctx.attr.plugins, | ||||
|     ) | ||||
| 
 | ||||
|     c_p = None | ||||
| 
 | ||||
|     if with_profiling: | ||||
|         c_p = hs.toolchain.actions.compile_library( | ||||
|             hs, | ||||
|             cc, | ||||
|             java, | ||||
|             dep_info, | ||||
|             plugin_dep_info, | ||||
|             srcs = srcs_files, | ||||
|             ls_modules = ctx.executable._ls_modules, | ||||
|             other_modules = other_modules, | ||||
|             exposed_modules_reexports = exposed_modules_reexports, | ||||
|             import_dir_map = import_dir_map, | ||||
|             # NOTE We must make the object files compiled without profiling | ||||
|             # available to this step for TH to work, presumably because GHC is | ||||
|             # linked against RTS without profiling. | ||||
|             extra_srcs = depset(transitive = [ | ||||
|                 depset(ctx.files.extra_srcs), | ||||
|                 depset([c.objects_dir]), | ||||
|             ]), | ||||
|             user_compile_flags = ctx.attr.compiler_flags, | ||||
|             # NOTE We can't have profiling and dynamic code at the | ||||
|             # same time, see: | ||||
|             # https://ghc.haskell.org/trac/ghc/ticket/15394 | ||||
|             with_shared = False, | ||||
|             with_profiling = True, | ||||
|             my_pkg_id = my_pkg_id, | ||||
|             plugins = ctx.attr.plugins, | ||||
|         ) | ||||
| 
 | ||||
|     static_library = link_library_static( | ||||
|         hs, | ||||
|         cc, | ||||
|         dep_info, | ||||
|         c.objects_dir, | ||||
|         my_pkg_id, | ||||
|         with_profiling = False, | ||||
|     ) | ||||
| 
 | ||||
|     if with_shared: | ||||
|         dynamic_library = link_library_dynamic( | ||||
|             hs, | ||||
|             cc, | ||||
|             dep_info, | ||||
|             depset(ctx.files.extra_srcs), | ||||
|             c.objects_dir, | ||||
|             my_pkg_id, | ||||
|         ) | ||||
|         dynamic_libraries = set.insert( | ||||
|             dep_info.dynamic_libraries, | ||||
|             dynamic_library, | ||||
|         ) | ||||
|     else: | ||||
|         dynamic_library = None | ||||
|         dynamic_libraries = dep_info.dynamic_libraries | ||||
| 
 | ||||
|     static_library_prof = None | ||||
|     if with_profiling: | ||||
|         static_library_prof = link_library_static( | ||||
|             hs, | ||||
|             cc, | ||||
|             dep_info, | ||||
|             c_p.objects_dir, | ||||
|             my_pkg_id, | ||||
|             with_profiling = True, | ||||
|         ) | ||||
| 
 | ||||
|     conf_file, cache_file = package( | ||||
|         hs, | ||||
|         dep_info, | ||||
|         c.interfaces_dir, | ||||
|         c_p.interfaces_dir if c_p != None else None, | ||||
|         static_library, | ||||
|         dynamic_library, | ||||
|         c.exposed_modules_file, | ||||
|         other_modules, | ||||
|         my_pkg_id, | ||||
|         static_library_prof = static_library_prof, | ||||
|     ) | ||||
| 
 | ||||
|     static_libraries_prof = dep_info.static_libraries_prof | ||||
| 
 | ||||
|     if static_library_prof != None: | ||||
|         static_libraries_prof = [static_library_prof] + dep_info.static_libraries_prof | ||||
| 
 | ||||
|     interface_dirs = set.union( | ||||
|         dep_info.interface_dirs, | ||||
|         set.singleton(c.interfaces_dir), | ||||
|     ) | ||||
| 
 | ||||
|     if c_p != None: | ||||
|         interface_dirs = set.mutable_union( | ||||
|             interface_dirs, | ||||
|             set.singleton(c_p.interfaces_dir), | ||||
|         ) | ||||
| 
 | ||||
|     version_macros = set.empty() | ||||
|     if version != None: | ||||
|         version_macros = set.singleton( | ||||
|             generate_version_macros(ctx, hs.name, version), | ||||
|         ) | ||||
| 
 | ||||
|     hs_info = HaskellInfo( | ||||
|         package_ids = set.insert(dep_info.package_ids, pkg_id.to_string(my_pkg_id)), | ||||
|         package_databases = set.insert(dep_info.package_databases, cache_file), | ||||
|         version_macros = version_macros, | ||||
|         source_files = c.source_files, | ||||
|         extra_source_files = c.extra_source_files, | ||||
|         import_dirs = c.import_dirs, | ||||
|         # NOTE We have to use lists for static libraries because the order is | ||||
|         # important for linker. Linker searches for unresolved symbols to the | ||||
|         # left, i.e. you first feed a library which has unresolved symbols and | ||||
|         # then you feed the library which resolves the symbols. | ||||
|         static_libraries = [static_library] + dep_info.static_libraries, | ||||
|         static_libraries_prof = static_libraries_prof, | ||||
|         dynamic_libraries = dynamic_libraries, | ||||
|         interface_dirs = interface_dirs, | ||||
|         compile_flags = c.compile_flags, | ||||
|         prebuilt_dependencies = dep_info.prebuilt_dependencies, | ||||
|         cc_dependencies = dep_info.cc_dependencies, | ||||
|         transitive_cc_dependencies = dep_info.transitive_cc_dependencies, | ||||
|     ) | ||||
|     lib_info = HaskellLibraryInfo( | ||||
|         package_id = pkg_id.to_string(my_pkg_id), | ||||
|         version = version, | ||||
|     ) | ||||
| 
 | ||||
|     dep_coverage_data = [] | ||||
|     for dep in ctx.attr.deps: | ||||
|         if HaskellCoverageInfo in dep: | ||||
|             dep_coverage_data += dep[HaskellCoverageInfo].coverage_data | ||||
| 
 | ||||
|     coverage_info = HaskellCoverageInfo( | ||||
|         coverage_data = dep_coverage_data + c.coverage_data, | ||||
|     ) | ||||
| 
 | ||||
|     target_files = depset([file for file in [static_library, dynamic_library] if file]) | ||||
| 
 | ||||
|     if hasattr(ctx, "outputs"): | ||||
|         build_haskell_repl( | ||||
|             hs, | ||||
|             ghci_script = ctx.file._ghci_script, | ||||
|             ghci_repl_wrapper = ctx.file._ghci_repl_wrapper, | ||||
|             repl_ghci_args = ctx.attr.repl_ghci_args, | ||||
|             user_compile_flags = ctx.attr.compiler_flags, | ||||
|             output = ctx.outputs.repl, | ||||
|             package_databases = dep_info.package_databases, | ||||
|             version = ctx.attr.version, | ||||
|             hs_info = hs_info, | ||||
|             lib_info = lib_info, | ||||
|         ) | ||||
| 
 | ||||
|         # XXX Temporary backwards compatibility hack. Remove eventually. | ||||
|         # See https://github.com/tweag/rules_haskell/pull/460. | ||||
|         ln(hs, ctx.outputs.repl, ctx.outputs.repl_deprecated) | ||||
| 
 | ||||
|         build_haskell_runghc( | ||||
|             hs, | ||||
|             runghc_wrapper = ctx.file._ghci_repl_wrapper, | ||||
|             extra_args = ctx.attr.runcompile_flags, | ||||
|             user_compile_flags = ctx.attr.compiler_flags, | ||||
|             output = ctx.outputs.runghc, | ||||
|             package_databases = dep_info.package_databases, | ||||
|             version = ctx.attr.version, | ||||
|             hs_info = hs_info, | ||||
|             lib_info = lib_info, | ||||
|         ) | ||||
| 
 | ||||
|     default_info = None | ||||
| 
 | ||||
|     if hasattr(ctx, "runfiles"): | ||||
|         default_info = DefaultInfo( | ||||
|             files = target_files, | ||||
|             runfiles = ctx.runfiles(collect_data = True), | ||||
|         ) | ||||
|     else: | ||||
|         default_info = DefaultInfo( | ||||
|             files = target_files, | ||||
|         ) | ||||
| 
 | ||||
|     # Create a CcInfo provider so that CC rules can work with | ||||
|     # a haskell library as if it was a regular CC one. | ||||
| 
 | ||||
|     # XXX Workaround https://github.com/bazelbuild/bazel/issues/6874. | ||||
|     # Should be find_cpp_toolchain() instead. | ||||
|     cc_toolchain = ctx.attr._cc_toolchain[cc_common.CcToolchainInfo] | ||||
|     feature_configuration = cc_common.configure_features( | ||||
|         cc_toolchain = cc_toolchain, | ||||
|         requested_features = ctx.features, | ||||
|         unsupported_features = ctx.disabled_features, | ||||
|     ) | ||||
|     library_to_link = cc_common.create_library_to_link( | ||||
|         actions = ctx.actions, | ||||
|         feature_configuration = feature_configuration, | ||||
|         dynamic_library = dynamic_library, | ||||
|         static_library = static_library, | ||||
|         cc_toolchain = cc_toolchain, | ||||
|     ) | ||||
|     compilation_context = cc_common.create_compilation_context() | ||||
|     linking_context = cc_common.create_linking_context( | ||||
|         libraries_to_link = [library_to_link], | ||||
|     ) | ||||
|     cc_info = cc_common.merge_cc_infos( | ||||
|         cc_infos = [ | ||||
|             CcInfo( | ||||
|                 compilation_context = compilation_context, | ||||
|                 linking_context = linking_context, | ||||
|             ), | ||||
|         ] + [dep[CcInfo] for dep in ctx.attr.deps if CcInfo in dep], | ||||
|     ) | ||||
| 
 | ||||
|     return [ | ||||
|         hs_info, | ||||
|         cc_info, | ||||
|         coverage_info, | ||||
|         default_info, | ||||
|         lib_info, | ||||
|     ] | ||||
| 
 | ||||
| def haskell_toolchain_library_impl(ctx): | ||||
|     hs = haskell_context(ctx) | ||||
| 
 | ||||
|     if ctx.attr.package: | ||||
|         package = ctx.attr.package | ||||
|     else: | ||||
|         package = ctx.label.name | ||||
| 
 | ||||
|     id_file = hs.actions.declare_file(target_unique_name(hs, "id")) | ||||
|     hs.actions.run_shell( | ||||
|         inputs = [hs.tools.ghc_pkg], | ||||
|         outputs = [id_file], | ||||
|         command = """ | ||||
|         "$1" --simple-output -v1 field "$2" id > "$3" | ||||
|         """, | ||||
|         arguments = [ | ||||
|             hs.tools.ghc_pkg.path, | ||||
|             package, | ||||
|             id_file.path, | ||||
|         ], | ||||
|     ) | ||||
| 
 | ||||
|     version_macros_file = hs.actions.declare_file("{}_version_macros.h".format(hs.name)) | ||||
|     hs.actions.run_shell( | ||||
|         inputs = [hs.tools.ghc_pkg, ctx.executable._version_macros], | ||||
|         outputs = [version_macros_file], | ||||
|         command = """ | ||||
|         "$1" \\ | ||||
|             `"$2" --simple-output -v1 field "$3" name` \\ | ||||
|             `"$2" --simple-output -v1 field "$3" version` \\ | ||||
|             > "$4" | ||||
|         """, | ||||
|         arguments = [ | ||||
|             ctx.executable._version_macros.path, | ||||
|             hs.tools.ghc_pkg.path, | ||||
|             package, | ||||
|             version_macros_file.path, | ||||
|         ], | ||||
|     ) | ||||
| 
 | ||||
|     prebuilt_package_info = HaskellPrebuiltPackageInfo( | ||||
|         package = package, | ||||
|         id_file = id_file, | ||||
|         version_macros_file = version_macros_file, | ||||
|     ) | ||||
| 
 | ||||
|     return [prebuilt_package_info] | ||||
| 
 | ||||
| def _exposed_modules_reexports(exports): | ||||
|     """Creates a ghc-pkg-compatible list of reexport declarations. | ||||
| 
 | ||||
|     A ghc-pkg registration file declares reexports as part of the | ||||
|     exposed-modules field in the following format: | ||||
| 
 | ||||
|     exposed-modules: A, B, C from pkg-c:C, D from pkg-d:Original.D | ||||
| 
 | ||||
|     Here, the Original.D module from pkg-d is renamed by virtue of a | ||||
|     different name being used before the "from" keyword. | ||||
| 
 | ||||
|     This function creates a ghc-pkg-compatible list of reexport declarations | ||||
|     (as shown above) from a dictionary mapping package targets to "Cabal-style" | ||||
|     reexported-modules declarations. That is, something like: | ||||
| 
 | ||||
|     { | ||||
|       ":pkg-c": "C", | ||||
|       ":pkg-d": "Original.D as D", | ||||
|       ":pkg-e": "E1, Original.E2 as E2", | ||||
|     } | ||||
| 
 | ||||
|     Args: | ||||
|       exports: a dictionary mapping package targets to "Cabal-style" | ||||
|                reexported-modules declarations. | ||||
| 
 | ||||
|     Returns: | ||||
|       a ghc-pkg-compatible list of reexport declarations. | ||||
|     """ | ||||
|     exposed_reexports = [] | ||||
|     for dep, cabal_decls in exports.items(): | ||||
|         for cabal_decl in cabal_decls.split(","): | ||||
|             stripped_cabal_decl = cabal_decl.strip() | ||||
|             cabal_decl_parts = stripped_cabal_decl.split(" as ") | ||||
|             original = cabal_decl_parts[0] | ||||
|             if len(cabal_decl_parts) == 2: | ||||
|                 reexported = cabal_decl_parts[1] | ||||
|             else: | ||||
|                 reexported = cabal_decl_parts[0] | ||||
| 
 | ||||
|             if HaskellPrebuiltPackageInfo in dep: | ||||
|                 pkg = dep[HaskellPrebuiltPackageInfo].package | ||||
|             elif HaskellLibraryInfo in dep: | ||||
|                 pkg = dep[HaskellLibraryInfo].package_id | ||||
| 
 | ||||
|             exposed_reexport = "{reexported} from {pkg}:{original}".format( | ||||
|                 reexported = reexported, | ||||
|                 pkg = pkg, | ||||
|                 original = original, | ||||
|             ) | ||||
| 
 | ||||
|             exposed_reexports.append(exposed_reexport) | ||||
| 
 | ||||
|     return exposed_reexports | ||||
							
								
								
									
										48
									
								
								third_party/bazel/rules_haskell/haskell/private/java.bzl
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								third_party/bazel/rules_haskell/haskell/private/java.bzl
									
										
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,48 @@ | |||
| """Interop with Java.""" | ||||
| 
 | ||||
| load("@bazel_skylib//lib:collections.bzl", "collections") | ||||
| 
 | ||||
| JavaInteropInfo = provider( | ||||
|     doc = "Information needed for interop with Java rules.", | ||||
|     fields = { | ||||
|         "inputs": "Files needed during build.", | ||||
|         "env": "Dict with env variables that should be set during build.", | ||||
|     }, | ||||
| ) | ||||
| 
 | ||||
| def java_interop_info(ctx): | ||||
|     """Gather information from any Java dependencies. | ||||
| 
 | ||||
|     Args: | ||||
|       ctx: Rule context. | ||||
| 
 | ||||
|     Returns: | ||||
|       JavaInteropInfo: Information needed for Java interop. | ||||
|     """ | ||||
| 
 | ||||
|     inputs = depset( | ||||
|         transitive = [ | ||||
|             # We only expose direct dependencies, though we could | ||||
|             # expose transitive ones as well. Only exposing the direct | ||||
|             # ones corresponds to Bazel's "strict Java dependencies" | ||||
|             # mode. See | ||||
|             # https://github.com/tweag/rules_haskell/issues/96. | ||||
|             dep[JavaInfo].compile_jars | ||||
|             for dep in ctx.attr.deps | ||||
|             if JavaInfo in dep | ||||
|         ], | ||||
|     ) | ||||
| 
 | ||||
|     env_dict = dict() | ||||
|     uniq_classpath = collections.uniq([ | ||||
|         f.path | ||||
|         for f in inputs | ||||
|     ]) | ||||
| 
 | ||||
|     if len(uniq_classpath) > 0: | ||||
|         env_dict["CLASSPATH"] = ":".join(uniq_classpath) | ||||
| 
 | ||||
|     return JavaInteropInfo( | ||||
|         inputs = inputs, | ||||
|         env = env_dict, | ||||
|     ) | ||||
							
								
								
									
										26
									
								
								third_party/bazel/rules_haskell/haskell/private/list.bzl
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								third_party/bazel/rules_haskell/haskell/private/list.bzl
									
										
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,26 @@ | |||
| """Helper functions on lists.""" | ||||
| 
 | ||||
| load(":private/set.bzl", "set") | ||||
| 
 | ||||
| def _dedup_on(f, list_): | ||||
|     """deduplicate `list_` by comparing the result of applying | ||||
|     f to each element (e.g. comparing sub fields) | ||||
| 
 | ||||
|     def compare_x(el): | ||||
|       return el.x | ||||
| 
 | ||||
|     dedup_on([struct(x=3), struct(x=4), struct(x=3)], compare_x) | ||||
|     => [struct(x=3), struct(x=4)] | ||||
|     """ | ||||
|     seen = set.empty() | ||||
|     deduped = [] | ||||
|     for el in list_: | ||||
|         by = f(el) | ||||
|         if not set.is_member(seen, by): | ||||
|             set.mutable_insert(seen, by) | ||||
|             deduped.append(el) | ||||
|     return deduped | ||||
| 
 | ||||
| list = struct( | ||||
|     dedup_on = _dedup_on, | ||||
| ) | ||||
							
								
								
									
										109
									
								
								third_party/bazel/rules_haskell/haskell/private/ls_modules.py
									
										
									
									
										vendored
									
									
										Executable file
									
								
							
							
						
						
									
										109
									
								
								third_party/bazel/rules_haskell/haskell/private/ls_modules.py
									
										
									
									
										vendored
									
									
										Executable file
									
								
							|  | @ -0,0 +1,109 @@ | |||
| #!/usr/bin/env python | ||||
| # | ||||
| # Create a list of exposed modules (including reexported modules) | ||||
| # given a directory full of interface files and the content of the | ||||
| # global package database (to mine the versions of all prebuilt | ||||
| # dependencies). The exposed modules are filtered using a provided | ||||
| # list of hidden modules, and augmented with reexport declarations. | ||||
| 
 | ||||
| from __future__ import unicode_literals, print_function | ||||
| 
 | ||||
| import collections | ||||
| import fnmatch | ||||
| import itertools | ||||
| import os | ||||
| import re | ||||
| import sys | ||||
| import io | ||||
| 
 | ||||
| if len(sys.argv) != 6: | ||||
|     sys.exit("Usage: %s <DIRECTORY> <GLOBAL_PKG_DB> <HIDDEN_MODS_FILE> <REEXPORTED_MODS_FILE> <RESULT_FILE>" % sys.argv[0]) | ||||
| 
 | ||||
| root = sys.argv[1] | ||||
| global_pkg_db_dump = sys.argv[2] | ||||
| hidden_modules_file = sys.argv[3] | ||||
| reexported_modules_file = sys.argv[4] | ||||
| results_file = sys.argv[5] | ||||
| 
 | ||||
| with io.open(global_pkg_db_dump, "r", encoding='utf8') as f: | ||||
|     names = [line.split()[1] for line in f if line.startswith("name:")] | ||||
|     f.seek(0) | ||||
|     ids = [line.split()[1] for line in f if line.startswith("id:")] | ||||
| 
 | ||||
|     # A few sanity checks. | ||||
|     assert len(names) == len(ids) | ||||
| 
 | ||||
|     # compute duplicate, i.e. package name associated with multiples ids | ||||
|     duplicates = set() | ||||
|     if len(names) != len(set(names)): | ||||
|         duplicates = set([ | ||||
|             name for name, count in collections.Counter(names).items() | ||||
|             if count > 1 | ||||
|         ]) | ||||
| 
 | ||||
|     # This associate pkg name to pkg id | ||||
|     pkg_ids_map = dict(zip(names, ids)) | ||||
| 
 | ||||
| with io.open(hidden_modules_file, "r", encoding='utf8') as f: | ||||
|     hidden_modules = [mod.strip() for mod in f.read().split(",")] | ||||
| 
 | ||||
| with io.open(reexported_modules_file, "r", encoding='utf8') as f: | ||||
|     raw_reexported_modules = ( | ||||
|         mod.strip() for mod in f.read().split(",") if mod.strip() | ||||
|     ) | ||||
|     # Substitute package ids for package names in reexports, because | ||||
|     # GHC really wants package ids. | ||||
|     regexp = re.compile("from (%s):" % "|".join(map(re.escape, pkg_ids_map))) | ||||
| 
 | ||||
|     def replace_pkg_by_pkgid(match): | ||||
|         pkgname = match.group(1) | ||||
| 
 | ||||
|         if pkgname in duplicates: | ||||
|             sys.exit( | ||||
|                 "\n".join([ | ||||
|                     "Multiple versions of the following packages installed: ", | ||||
|                     ", ".join(duplicates), | ||||
|                     "\nThe following was explictly used: " + pkgname, | ||||
|                     "\nThis is not currently supported.", | ||||
|                 ]) | ||||
|             ) | ||||
| 
 | ||||
|         return "from %s:" % pkg_ids_map[pkgname] | ||||
| 
 | ||||
|     reexported_modules = ( | ||||
|         regexp.sub(replace_pkg_by_pkgid, mod) | ||||
|         for mod in raw_reexported_modules | ||||
|     ) | ||||
| 
 | ||||
| def handle_walk_error(e): | ||||
|     print(""" | ||||
| Failed to list interface files: | ||||
|     {} | ||||
| On Windows you may need to enable long file path support: | ||||
|     Set-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\FileSystem' -Name 'LongPathsEnabled' -Value 1 | ||||
|     """.strip().format(e), file=sys.stderr) | ||||
|     exit(1) | ||||
| 
 | ||||
| interface_files = ( | ||||
|     os.path.join(path, f) | ||||
|     for path, dirs, files in os.walk(root, onerror=handle_walk_error) | ||||
|     for f in fnmatch.filter(files, '*.hi') | ||||
| ) | ||||
| 
 | ||||
| modules = ( | ||||
|     # replace directory separators by . to generate module names | ||||
|     # / and \ are respectively the separators for unix (linux / darwin) and windows systems | ||||
|     os.path.splitext(os.path.relpath(f, start=root))[0] | ||||
|         .replace("/",".") | ||||
|         .replace("\\",".") | ||||
|     for f in interface_files | ||||
| ) | ||||
| 
 | ||||
| exposed_modules = ( | ||||
|     m | ||||
|     for m in modules | ||||
|     if m not in hidden_modules | ||||
| ) | ||||
| 
 | ||||
| with io.open(results_file, "w", encoding='utf8') as f: | ||||
|     f.write(", ".join(itertools.chain(exposed_modules, reexported_modules))) | ||||
							
								
								
									
										12
									
								
								third_party/bazel/rules_haskell/haskell/private/mode.bzl
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								third_party/bazel/rules_haskell/haskell/private/mode.bzl
									
										
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,12 @@ | |||
| """Compilation modes.""" | ||||
| 
 | ||||
| def is_profiling_enabled(hs): | ||||
|     """Check whether profiling mode is enabled. | ||||
| 
 | ||||
|     Args: | ||||
|       hs: Haskell context. | ||||
| 
 | ||||
|     Returns: | ||||
|       bool: True if the mode is enabled, False otherwise. | ||||
|     """ | ||||
|     return hs.mode == "dbg" | ||||
							
								
								
									
										313
									
								
								third_party/bazel/rules_haskell/haskell/private/osx_cc_wrapper.sh.tpl
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										313
									
								
								third_party/bazel/rules_haskell/haskell/private/osx_cc_wrapper.sh.tpl
									
										
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,313 @@ | |||
| #!/bin/bash | ||||
| # | ||||
| # Copyright 2015 The Bazel Authors. All rights reserved. | ||||
| # | ||||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| # you may not use this file except in compliance with the License. | ||||
| # You may obtain a copy of the License at | ||||
| # | ||||
| #    http://www.apache.org/licenses/LICENSE-2.0 | ||||
| # | ||||
| # Unless required by applicable law or agreed to in writing, software | ||||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| # See the License for the specific language governing permissions and | ||||
| # limitations under the License. | ||||
| # | ||||
| # This is a wrapper script around gcc/clang that adjusts linker flags for | ||||
| # Haskell library and binary targets. | ||||
| # | ||||
| # Load commands that attempt to load dynamic libraries relative to the working | ||||
| # directory in their package output path (bazel-out/...) are converted to load | ||||
| # commands relative to @rpath. rules_haskell passes the corresponding | ||||
| # -Wl,-rpath,... flags itself. | ||||
| # | ||||
| # rpath commands that attempt to add rpaths relative to the working directory | ||||
| # to look for libraries in their package output path (bazel-out/...) are | ||||
| # omitted, since rules_haskell adds more appropriate rpaths itself. | ||||
| # | ||||
| # GHC generates intermediate dynamic libraries outside the build tree. | ||||
| # Additional RPATH entries are provided for those to make dynamic library | ||||
| # dependencies in the Bazel build tree available at runtime. | ||||
| # | ||||
| # See https://blogs.oracle.com/dipol/entry/dynamic_libraries_rpath_and_mac | ||||
| # on how to set those paths for Mach-O binaries. | ||||
| # | ||||
| set -euo pipefail | ||||
| 
 | ||||
| INSTALL_NAME_TOOL="/usr/bin/install_name_tool" | ||||
| OTOOL="/usr/bin/otool" | ||||
| 
 | ||||
| # Collect arguments to forward in a fresh response file. | ||||
| RESPONSE_FILE="$(mktemp osx_cc_args_XXXX.rsp)" | ||||
| rm_response_file() { | ||||
|     rm -f "$RESPONSE_FILE" | ||||
| } | ||||
| trap rm_response_file EXIT | ||||
| 
 | ||||
| add_args() { | ||||
|    # Add the given arguments to the fresh response file. We follow GHC's | ||||
|    # example in storing one argument per line, wrapped in double quotes. Double | ||||
|    # quotes in the argument itself are escaped. | ||||
|    for arg in "$@"; do | ||||
|        printf '"%s"\n' "${arg//\"/\\\"}" >> "$RESPONSE_FILE" | ||||
|    done | ||||
| } | ||||
| 
 | ||||
| # Collect library, library dir, and rpath arguments. | ||||
| LIBS=() | ||||
| LIB_DIRS=() | ||||
| RPATHS=() | ||||
| 
 | ||||
| # Parser state. | ||||
| # Parsing response file - unquote arguments. | ||||
| QUOTES= | ||||
| # Upcoming linker argument. | ||||
| LINKER= | ||||
| # Upcoming rpath argument. | ||||
| RPATH= | ||||
| # Upcoming install-name argument. | ||||
| INSTALL= | ||||
| # Upcoming output argument. | ||||
| OUTPUT= | ||||
| 
 | ||||
| parse_arg() { | ||||
|     # Parse the given argument. Decide whether to pass it on to the compiler, | ||||
|     # and how it affects the parser state. | ||||
|     local arg="$1" | ||||
|     # Unquote response file arguments. | ||||
|     if [[ "$QUOTES" = "1" && "$arg" =~ ^\"(.*)\"$ ]]; then | ||||
|         # Take GHC's argument quoting into account when parsing a response | ||||
|         # file. Note, no indication was found that GHC would pass multiline | ||||
|         # arguments, or insert escape codes into the quoted arguments. If you | ||||
|         # observe ill-formed arguments being passed to the compiler, then this | ||||
|         # logic may need to be extended. | ||||
|         arg="${BASH_REMATCH[1]}" | ||||
|     fi | ||||
|     # Parse given argument. | ||||
|     if [[ "$OUTPUT" = "1" ]]; then | ||||
|         # The previous argument was -o. Read output file. | ||||
|         OUTPUT="$arg" | ||||
|         add_args "$arg" | ||||
|     elif [[ "$LINKER" = "1" ]]; then | ||||
|         # The previous argument was -Xlinker. Read linker argument. | ||||
|         if [[ "$RPATH" = "1" ]]; then | ||||
|             # The previous argument was -rpath. Read RPATH. | ||||
|             parse_rpath "$arg" | ||||
|             RPATH=0 | ||||
|         elif [[ "$arg" = "-rpath" ]]; then | ||||
|             # rpath is coming | ||||
|             RPATH=1 | ||||
|         else | ||||
|             # Unrecognized linker argument. Pass it on. | ||||
|             add_args "-Xlinker" "$arg" | ||||
|         fi | ||||
|         LINKER= | ||||
|     elif [[ "$INSTALL" = "1" ]]; then | ||||
|         INSTALL= | ||||
|         add_args "$arg" | ||||
|     elif [[ "$arg" =~ ^@(.*)$ ]]; then | ||||
|         # Handle response file argument. Parse the arguments contained in the | ||||
|         # response file one by one. Take GHC's argument quoting into account. | ||||
|         # Note, assumes that response file arguments are not nested in other | ||||
|         # response files. | ||||
|         QUOTES=1 | ||||
|         while read line; do | ||||
|             parse_arg "$line" | ||||
|         done < "${BASH_REMATCH[1]}" | ||||
|         QUOTES= | ||||
|     elif [[ "$arg" = "-install_name" ]]; then | ||||
|         # Install name is coming. We don't use it, but it can start with an @ | ||||
|         # and be mistaken for a response file. | ||||
|         INSTALL=1 | ||||
|         add_args "$arg" | ||||
|     elif [[ "$arg" = "-o" ]]; then | ||||
|         # output is coming | ||||
|         OUTPUT=1 | ||||
|         add_args "$arg" | ||||
|     elif [[ "$arg" = "-Xlinker" ]]; then | ||||
|         # linker flag is coming | ||||
|         LINKER=1 | ||||
|     elif [[ "$arg" =~ ^-l(.*)$ ]]; then | ||||
|         LIBS+=("${BASH_REMATCH[1]}") | ||||
|         add_args "$arg" | ||||
|     elif [[ "$arg" =~ ^-L(.*)$ ]]; then | ||||
|         LIB_DIRS+=("${BASH_REMATCH[1]}") | ||||
|         add_args "$arg" | ||||
|     elif [[ "$arg" =~ ^-Wl,-rpath,(.*)$ ]]; then | ||||
|         parse_rpath "${BASH_REMATCH[1]}" | ||||
|     else | ||||
|         # Unrecognized argument. Pass it on. | ||||
|         add_args "$arg" | ||||
|     fi | ||||
| } | ||||
| 
 | ||||
| parse_rpath() { | ||||
|     # Parse the given -rpath argument and decide whether it should be | ||||
|     # forwarded to the compiler/linker. | ||||
|     local rpath="$1" | ||||
|     if [[ "$rpath" =~ ^/ || "$rpath" =~ ^@ ]]; then | ||||
|         # Absolute rpaths or rpaths relative to @loader_path or similar, are | ||||
|         # passed on to the linker. Other relative rpaths are dropped, these | ||||
|         # are auto-generated by GHC, but are useless because rules_haskell | ||||
|         # constructs dedicated rpaths to the _solib or _hssolib directory. | ||||
|         # See https://github.com/tweag/rules_haskell/issues/689 | ||||
|         add_args "-Wl,-rpath,$rpath" | ||||
|         RPATHS+=("$rpath") | ||||
|     fi | ||||
| } | ||||
| 
 | ||||
| # Parse all given arguments. | ||||
| for arg in "$@"; do | ||||
|     parse_arg "$arg" | ||||
| done | ||||
| 
 | ||||
| get_library_in() { | ||||
|     # Find the given library in the given directory. | ||||
|     # Returns empty string if the library is not found. | ||||
|     local lib="$1" | ||||
|     local dir="$2" | ||||
|     local solib="${dir}${dir:+/}lib${lib}.so" | ||||
|     local dylib="${dir}${dir:+/}lib${lib}.dylib" | ||||
|     if [[ -f "$solib" ]]; then | ||||
|         echo "$solib" | ||||
|     elif [[ -f "$dylib" ]]; then | ||||
|         echo "$dylib" | ||||
|     fi | ||||
| } | ||||
| 
 | ||||
| get_library_path() { | ||||
|     # Find the given library in the specified library search paths. | ||||
|     # Returns empty string if the library is not found. | ||||
|     if [[ ${#LIB_DIRS[@]} -gt 0 ]]; then | ||||
|         local libpath | ||||
|         for libdir in "${LIB_DIRS[@]}"; do | ||||
|             libpath="$(get_library_in "$1" "$libdir")" | ||||
|             if [[ -n "$libpath" ]]; then | ||||
|                 echo "$libpath" | ||||
|                 return | ||||
|             fi | ||||
|         done | ||||
|     fi | ||||
| } | ||||
| 
 | ||||
| resolve_rpath() { | ||||
|     # Resolve the given rpath. I.e. if it is an absolute path, just return it. | ||||
|     # If it is relative to the output, then prepend the output path. | ||||
|     local rpath="$1" | ||||
|     if [[ "$rpath" =~ ^/ ]]; then | ||||
|         echo "$rpath" | ||||
|     elif [[ "$rpath" =~ ^@loader_path/(.*)$ || "$rpath" =~ ^@executable_path/(.*)$ ]]; then | ||||
|         echo "$(dirname "$OUTPUT")/${BASH_REMATCH[1]}" | ||||
|     else | ||||
|         echo "$rpath" | ||||
|     fi | ||||
| } | ||||
| 
 | ||||
| get_library_rpath() { | ||||
|     # Find the given library in the specified rpaths. | ||||
|     # Returns empty string if the library is not found. | ||||
|     if [[ ${#RPATHS[@]} -gt 0 ]]; then | ||||
|         local libdir libpath | ||||
|         for rpath in "${RPATHS[@]}"; do | ||||
|             libdir="$(resolve_rpath "$rpath")" | ||||
|             libpath="$(get_library_in "$1" "$libdir")" | ||||
|             if [[ -n "$libpath" ]]; then | ||||
|                 echo "$libpath" | ||||
|                 return | ||||
|             fi | ||||
|         done | ||||
|     fi | ||||
| } | ||||
| 
 | ||||
| get_library_name() { | ||||
|     # Get the "library name" of the given library. | ||||
|     "$OTOOL" -D "$1" | tail -1 | ||||
| } | ||||
| 
 | ||||
| relpath() { | ||||
|     # Find relative path from the first to the second path. Assuming the first | ||||
|     # is a directory. If either is an absolute path, then we return the | ||||
|     # absolute path to the second. | ||||
|     local from="$1" | ||||
|     local to="$2" | ||||
|     if [[ "$to" =~ ^/ ]]; then | ||||
|         echo "$to" | ||||
|     elif [[ "$from" =~ ^/ ]]; then | ||||
|         echo "$PWD/$to" | ||||
|     else | ||||
|         # Split path and store components in bash array. | ||||
|         IFS=/ read -a fromarr <<<"$from" | ||||
|         IFS=/ read -a toarr <<<"$to" | ||||
|         # Drop common prefix. | ||||
|         for ((i=0; i < ${#fromarr[@]}; ++i)); do | ||||
|             if [[ "${fromarr[$i]}" != "${toarr[$i]}" ]]; then | ||||
|                 break | ||||
|             fi | ||||
|         done | ||||
|         # Construct relative path. | ||||
|         local common=$i | ||||
|         local out= | ||||
|         for ((i=$common; i < ${#fromarr[@]}; ++i)); do | ||||
|             out="$out${out:+/}.." | ||||
|         done | ||||
|         for ((i=$common; i < ${#toarr[@]}; ++i)); do | ||||
|             out="$out${out:+/}${toarr[$i]}" | ||||
|         done | ||||
|         echo $out | ||||
|     fi | ||||
| } | ||||
| 
 | ||||
| generate_rpath() { | ||||
|     # Generate an rpath entry for the given library path. | ||||
|     local rpath="$(relpath "$(dirname "$OUTPUT")" "$(dirname "$1")")" | ||||
|     if [[ "$rpath" =~ ^/ ]]; then | ||||
|         echo "$rpath" | ||||
|     else | ||||
|         # Relative rpaths are relative to the binary. | ||||
|         echo "@loader_path${rpath:+/}$rpath" | ||||
|     fi | ||||
| } | ||||
| 
 | ||||
| if [[ ! "$OUTPUT" =~ ^bazel-out/ && ${#LIBS[@]} -gt 0 ]]; then | ||||
|     # GHC generates temporary dynamic libraries during compilation outside of | ||||
|     # the build directory. References to dynamic C libraries are broken in this | ||||
|     # case. Here we add additional RPATHs to fix these references. The Hazel | ||||
|     # package for swagger2 is an example that triggers this issue. | ||||
|     for lib in "${LIBS[@]}"; do | ||||
|         librpath="$(get_library_rpath "$lib")" | ||||
|         if [[ -z "$librpath" ]]; then | ||||
|             # The given library was not found in any of the rpaths. | ||||
|             # Find it in the library search paths. | ||||
|             libpath="$(get_library_path "$lib")" | ||||
|             if [[ "$libpath" =~ ^bazel-out/ ]]; then | ||||
|                 # The library is Bazel generated and loaded relative to PWD. | ||||
|                 # Add an RPATH entry, so it is found at runtime. | ||||
|                 rpath="$(generate_rpath "$libpath")" | ||||
|                 parse_rpath "$rpath" | ||||
|             fi | ||||
|         fi | ||||
|     done | ||||
| fi | ||||
| 
 | ||||
| # Call the C++ compiler with the fresh response file. | ||||
| %{cc} "@$RESPONSE_FILE" | ||||
| 
 | ||||
| if [[ ${#LIBS[@]} -gt 0 ]]; then | ||||
|     # Replace load commands relative to the working directory, by load commands | ||||
|     # relative to the rpath, if the library can be found relative to an rpath. | ||||
|     for lib in "${LIBS[@]}"; do | ||||
|         librpath="$(get_library_rpath "$lib")" | ||||
|         if [[ -n "$librpath" ]]; then | ||||
|             libname="$(get_library_name "$librpath")" | ||||
|             if [[ "$libname" =~ ^bazel-out/ ]]; then | ||||
|                 "${INSTALL_NAME_TOOL}" -change \ | ||||
|                     "$libname" \ | ||||
|                     "@rpath/$(basename "$librpath")" \ | ||||
|                     "$OUTPUT" | ||||
|             fi | ||||
|         fi | ||||
|     done | ||||
| fi | ||||
| 
 | ||||
| # vim: ft=sh | ||||
							
								
								
									
										94
									
								
								third_party/bazel/rules_haskell/haskell/private/packages.bzl
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										94
									
								
								third_party/bazel/rules_haskell/haskell/private/packages.bzl
									
										
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,94 @@ | |||
| """Package list handling""" | ||||
| 
 | ||||
| load(":private/set.bzl", "set") | ||||
| 
 | ||||
| def pkg_info_to_compile_flags(pkg_info, for_plugin = False): | ||||
|     """Map package info to GHC command-line arguments. | ||||
| 
 | ||||
|     Args: | ||||
|       pkg_info: Package info collected by `ghc_info()`. | ||||
|       for_plugin: Whether the package is a plugin dependency. | ||||
| 
 | ||||
|     Returns: | ||||
|       The list of command-line arguments that should be passed to GHC. | ||||
|     """ | ||||
|     namespace = "plugin-" if for_plugin else "" | ||||
|     args = [ | ||||
|         # In compile.bzl, we pass this just before all -package-id | ||||
|         # arguments. Not doing so leads to bizarre compile-time failures. | ||||
|         # It turns out that equally, not doing so leads to bizarre | ||||
|         # link-time failures. See | ||||
|         # https://github.com/tweag/rules_haskell/issues/395. | ||||
|         "-hide-all-{}packages".format(namespace), | ||||
|     ] | ||||
| 
 | ||||
|     if not pkg_info.has_version: | ||||
|         args.extend([ | ||||
|             # Macro version are disabled for all packages by default | ||||
|             # and enabled for package with version | ||||
|             # see https://github.com/tweag/rules_haskell/issues/414 | ||||
|             "-fno-version-macros", | ||||
|         ]) | ||||
| 
 | ||||
|     for package in pkg_info.packages: | ||||
|         args.extend(["-{}package".format(namespace), package]) | ||||
| 
 | ||||
|     for package_id in pkg_info.package_ids: | ||||
|         args.extend(["-{}package-id".format(namespace), package_id]) | ||||
| 
 | ||||
|     for package_db in pkg_info.package_dbs: | ||||
|         args.extend(["-package-db", package_db]) | ||||
| 
 | ||||
|     return args | ||||
| 
 | ||||
| def expose_packages(hs_info, lib_info, use_direct, use_my_pkg_id, custom_package_databases, version): | ||||
|     """ | ||||
|     Returns the information that is needed by GHC in order to enable haskell | ||||
|     packages. | ||||
| 
 | ||||
|     hs_info: is common to all builds | ||||
|     version: if the rule contains a version, we will export the CPP version macro | ||||
| 
 | ||||
|     All the other arguments are not understood well: | ||||
| 
 | ||||
|     lib_info: only used for repl and linter | ||||
|     use_direct: only used for repl and linter | ||||
|     use_my_pkg_id: only used for one specific task in compile.bzl | ||||
|     custom_package_databases: override the package_databases of hs_info, used only by the repl | ||||
|     """ | ||||
|     has_version = version != None and version != "" | ||||
| 
 | ||||
|     # Expose all prebuilt dependencies | ||||
|     # | ||||
|     # We have to remember to specify all (transitive) wired-in | ||||
|     # dependencies or we can't find objects for linking | ||||
|     # | ||||
|     # Set use_direct if hs_info does not have a direct_prebuilt_deps field. | ||||
|     packages = [] | ||||
|     for prebuilt_dep in set.to_list(hs_info.direct_prebuilt_deps if use_direct else hs_info.prebuilt_dependencies): | ||||
|         packages.append(prebuilt_dep.package) | ||||
| 
 | ||||
|     # Expose all bazel dependencies | ||||
|     package_ids = [] | ||||
|     for package in set.to_list(hs_info.package_ids): | ||||
|         # XXX: repl and lint uses this lib_info flags | ||||
|         # It is set to None in all other usage of this function | ||||
|         # TODO: find the meaning of this flag | ||||
|         if lib_info == None or package != lib_info.package_id: | ||||
|             # XXX: use_my_pkg_id is not None only in compile.bzl | ||||
|             if (use_my_pkg_id == None) or package != use_my_pkg_id: | ||||
|                 package_ids.append(package) | ||||
| 
 | ||||
|     # Only include package DBs for deps, prebuilt deps should be found | ||||
|     # auto-magically by GHC | ||||
|     package_dbs = [] | ||||
|     for cache in set.to_list(hs_info.package_databases if not custom_package_databases else custom_package_databases): | ||||
|         package_dbs.append(cache.dirname) | ||||
| 
 | ||||
|     ghc_info = struct( | ||||
|         has_version = has_version, | ||||
|         packages = packages, | ||||
|         package_ids = package_ids, | ||||
|         package_dbs = package_dbs, | ||||
|     ) | ||||
|     return ghc_info | ||||
							
								
								
									
										471
									
								
								third_party/bazel/rules_haskell/haskell/private/path_utils.bzl
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										471
									
								
								third_party/bazel/rules_haskell/haskell/private/path_utils.bzl
									
										
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,471 @@ | |||
| """Utilities for module and path manipulations.""" | ||||
| 
 | ||||
| load("@bazel_skylib//lib:paths.bzl", "paths") | ||||
| load(":private/set.bzl", "set") | ||||
| 
 | ||||
| def module_name(hs, f, rel_path = None): | ||||
|     """Given Haskell source file path, turn it into a dot-separated module name. | ||||
| 
 | ||||
|     module_name( | ||||
|       hs, | ||||
|       "some-workspace/some-package/src/Foo/Bar/Baz.hs", | ||||
|     ) => "Foo.Bar.Baz" | ||||
| 
 | ||||
|     Args: | ||||
|       hs:  Haskell context. | ||||
|       f:   Haskell source file. | ||||
|       rel_path: Explicit relative path from import root to the module, or None | ||||
|         if it should be deduced. | ||||
| 
 | ||||
|     Returns: | ||||
|       string: Haskell module name. | ||||
|     """ | ||||
| 
 | ||||
|     rpath = rel_path | ||||
| 
 | ||||
|     if not rpath: | ||||
|         rpath = _rel_path_to_module(hs, f) | ||||
| 
 | ||||
|     (hsmod, _) = paths.split_extension(rpath.replace("/", ".")) | ||||
|     return hsmod | ||||
| 
 | ||||
| def target_unique_name(hs, name_prefix): | ||||
|     """Make a target-unique name. | ||||
| 
 | ||||
|     `name_prefix` is made target-unique by adding a rule name | ||||
|     suffix to it. This means that given two different rules, the same | ||||
|     `name_prefix` is distinct. Note that this is does not disambiguate two | ||||
|     names within the same rule. Given a haskell_library with name foo | ||||
|     you could expect: | ||||
| 
 | ||||
|     target_unique_name(hs, "libdir") => "libdir-foo" | ||||
| 
 | ||||
|     This allows two rules using same name_prefix being built in same | ||||
|     environment to avoid name clashes of their output files and directories. | ||||
| 
 | ||||
|     Args: | ||||
|       hs:          Haskell context. | ||||
|       name_prefix: Template for the name. | ||||
| 
 | ||||
|     Returns: | ||||
|       string: Target-unique name_prefix. | ||||
|     """ | ||||
|     return "{0}-{1}".format(name_prefix, hs.name) | ||||
| 
 | ||||
| def module_unique_name(hs, source_file, name_prefix): | ||||
|     """Make a target- and module- unique name. | ||||
| 
 | ||||
|     module_unique_name( | ||||
|       hs, | ||||
|       "some-workspace/some-package/src/Foo/Bar/Baz.hs", | ||||
|       "libdir" | ||||
|     ) => "libdir-foo-Foo.Bar.Baz" | ||||
| 
 | ||||
|     This is quite similar to `target_unique_name` but also uses a path built | ||||
|     from `source_file` to prevent clashes with other names produced using the | ||||
|     same `name_prefix`. | ||||
| 
 | ||||
|     Args: | ||||
|       hs:          Haskell context. | ||||
|       source_file: Source file name. | ||||
|       name_prefix: Template for the name. | ||||
| 
 | ||||
|     Returns: | ||||
|       string: Target- and source-unique name. | ||||
|     """ | ||||
|     return "{0}-{1}".format( | ||||
|         target_unique_name(hs, name_prefix), | ||||
|         module_name(hs, source_file), | ||||
|     ) | ||||
| 
 | ||||
| def declare_compiled(hs, src, ext, directory = None, rel_path = None): | ||||
|     """Given a Haskell-ish source file, declare its output. | ||||
| 
 | ||||
|     Args: | ||||
|       hs: Haskell context. | ||||
|       src: Haskell source file. | ||||
|       ext: New extension. | ||||
|       directory: String, directory prefix the new file should live in. | ||||
|       rel_path: Explicit relative path from import root to the module, or None | ||||
|         if it should be deduced. | ||||
| 
 | ||||
|     Returns: | ||||
|       File: Declared output file living in `directory` with given `ext`. | ||||
|     """ | ||||
| 
 | ||||
|     rpath = rel_path | ||||
| 
 | ||||
|     if not rpath: | ||||
|         rpath = _rel_path_to_module(hs, src) | ||||
| 
 | ||||
|     fp = paths.replace_extension(rpath, ext) | ||||
|     fp_with_dir = fp if directory == None else paths.join(directory, fp) | ||||
| 
 | ||||
|     return hs.actions.declare_file(fp_with_dir) | ||||
| 
 | ||||
| def make_path(libs, prefix = None, sep = None): | ||||
|     """Return a string value for using as LD_LIBRARY_PATH or similar. | ||||
| 
 | ||||
|     Args: | ||||
|       libs: List of library files that should be available | ||||
|       prefix: String, an optional prefix to add to every path. | ||||
|       sep: String, the path separator, defaults to ":". | ||||
| 
 | ||||
|     Returns: | ||||
|       String: paths to the given library directories separated by ":". | ||||
|     """ | ||||
|     r = set.empty() | ||||
| 
 | ||||
|     sep = sep if sep else ":" | ||||
| 
 | ||||
|     for lib in libs: | ||||
|         lib_dir = paths.dirname(lib.path) | ||||
|         if prefix: | ||||
|             lib_dir = paths.join(prefix, lib_dir) | ||||
| 
 | ||||
|         set.mutable_insert(r, lib_dir) | ||||
| 
 | ||||
|     return sep.join(set.to_list(r)) | ||||
| 
 | ||||
| def darwin_convert_to_dylibs(hs, libs): | ||||
|     """Convert .so dynamic libraries to .dylib. | ||||
| 
 | ||||
|     Bazel's cc_library rule will create .so files for dynamic libraries even | ||||
|     on MacOS. GHC's builtin linker, which is used during compilation, GHCi, | ||||
|     or doctests, hard-codes the assumption that all dynamic libraries on MacOS | ||||
|     end on .dylib. This function serves as an adaptor and produces symlinks | ||||
|     from a .dylib version to the .so version for every dynamic library | ||||
|     dependencies that does not end on .dylib. | ||||
| 
 | ||||
|     Args: | ||||
|       hs: Haskell context. | ||||
|       libs: List of library files dynamic or static. | ||||
| 
 | ||||
|     Returns: | ||||
|       List of library files where all dynamic libraries end on .dylib. | ||||
|     """ | ||||
|     lib_prefix = "_dylibs" | ||||
|     new_libs = [] | ||||
|     for lib in libs: | ||||
|         if is_shared_library(lib) and lib.extension != "dylib": | ||||
|             dylib_name = paths.join( | ||||
|                 target_unique_name(hs, lib_prefix), | ||||
|                 lib.dirname, | ||||
|                 "lib" + get_lib_name(lib) + ".dylib", | ||||
|             ) | ||||
|             dylib = hs.actions.declare_file(dylib_name) | ||||
|             ln(hs, lib, dylib) | ||||
|             new_libs.append(dylib) | ||||
|         else: | ||||
|             new_libs.append(lib) | ||||
|     return new_libs | ||||
| 
 | ||||
| def windows_convert_to_dlls(hs, libs): | ||||
|     """Convert .so dynamic libraries to .dll. | ||||
| 
 | ||||
|     Bazel's cc_library rule will create .so files for dynamic libraries even | ||||
|     on Windows. GHC's builtin linker, which is used during compilation, GHCi, | ||||
|     or doctests, hard-codes the assumption that all dynamic libraries on Windows | ||||
|     end on .dll. This function serves as an adaptor and produces symlinks | ||||
|     from a .dll version to the .so version for every dynamic library | ||||
|     dependencies that does not end on .dll. | ||||
| 
 | ||||
|     Args: | ||||
|       hs: Haskell context. | ||||
|       libs: List of library files dynamic or static. | ||||
| 
 | ||||
|     Returns: | ||||
|       List of library files where all dynamic libraries end on .dll. | ||||
|     """ | ||||
|     lib_prefix = "_dlls" | ||||
|     new_libs = [] | ||||
|     for lib in libs: | ||||
|         if is_shared_library(lib) and lib.extension != "dll": | ||||
|             dll_name = paths.join( | ||||
|                 target_unique_name(hs, lib_prefix), | ||||
|                 paths.dirname(lib.short_path), | ||||
|                 "lib" + get_lib_name(lib) + ".dll", | ||||
|             ) | ||||
|             dll = hs.actions.declare_file(dll_name) | ||||
|             ln(hs, lib, dll) | ||||
|             new_libs.append(dll) | ||||
|         else: | ||||
|             new_libs.append(lib) | ||||
|     return new_libs | ||||
| 
 | ||||
| def get_lib_name(lib): | ||||
|     """Return name of library by dropping extension and "lib" prefix. | ||||
| 
 | ||||
|     Args: | ||||
|       lib: The library File. | ||||
| 
 | ||||
|     Returns: | ||||
|       String: name of library. | ||||
|     """ | ||||
| 
 | ||||
|     base = lib.basename[3:] if lib.basename[:3] == "lib" else lib.basename | ||||
|     n = base.find(".so.") | ||||
|     end = paths.replace_extension(base, "") if n == -1 else base[:n] | ||||
|     return end | ||||
| 
 | ||||
| def link_libraries(libs_to_link, args): | ||||
|     """Add linker flags to link against the given libraries. | ||||
| 
 | ||||
|     Args: | ||||
|       libs_to_link: List of library Files. | ||||
|       args: Append arguments to this list. | ||||
| 
 | ||||
|     Returns: | ||||
|       List of library names that were linked. | ||||
| 
 | ||||
|     """ | ||||
|     seen_libs = set.empty() | ||||
|     libraries = [] | ||||
|     for lib in libs_to_link: | ||||
|         lib_name = get_lib_name(lib) | ||||
|         if not set.is_member(seen_libs, lib_name): | ||||
|             set.mutable_insert(seen_libs, lib_name) | ||||
|             args += ["-l{0}".format(lib_name)] | ||||
|             libraries.append(lib_name) | ||||
| 
 | ||||
| def is_shared_library(f): | ||||
|     """Check if the given File is a shared library. | ||||
| 
 | ||||
|     Args: | ||||
|       f: The File to check. | ||||
| 
 | ||||
|     Returns: | ||||
|       Bool: True if the given file `f` is a shared library, False otherwise. | ||||
|     """ | ||||
|     return f.extension in ["so", "dylib"] or f.basename.find(".so.") != -1 | ||||
| 
 | ||||
| def is_static_library(f): | ||||
|     """Check if the given File is a static library. | ||||
| 
 | ||||
|     Args: | ||||
|       f: The File to check. | ||||
| 
 | ||||
|     Returns: | ||||
|       Bool: True if the given file `f` is a static library, False otherwise. | ||||
|     """ | ||||
|     return f.extension in ["a"] | ||||
| 
 | ||||
| def _rel_path_to_module(hs, f): | ||||
|     """Make given file name relative to the directory where the module hierarchy | ||||
|     starts. | ||||
| 
 | ||||
|     _rel_path_to_module( | ||||
|       "some-workspace/some-package/src/Foo/Bar/Baz.hs" | ||||
|     ) => "Foo/Bar/Baz.hs" | ||||
| 
 | ||||
|     Args: | ||||
|       hs:  Haskell context. | ||||
|       f:   Haskell source file. | ||||
| 
 | ||||
|     Returns: | ||||
|       string: Relative path to module file. | ||||
|     """ | ||||
| 
 | ||||
|     # If it's a generated file, strip off the bin or genfiles prefix. | ||||
|     path = f.path | ||||
|     if path.startswith(hs.bin_dir.path): | ||||
|         path = paths.relativize(path, hs.bin_dir.path) | ||||
|     elif path.startswith(hs.genfiles_dir.path): | ||||
|         path = paths.relativize(path, hs.genfiles_dir.path) | ||||
| 
 | ||||
|     return paths.relativize(path, hs.src_root) | ||||
| 
 | ||||
| # TODO Consider merging with paths.relativize. See | ||||
| # https://github.com/bazelbuild/bazel-skylib/pull/44. | ||||
| def _truly_relativize(target, relative_to): | ||||
|     """Return a relative path to `target` from `relative_to`. | ||||
| 
 | ||||
|     Args: | ||||
|       target: string, path to directory we want to get relative path to. | ||||
|       relative_to: string, path to directory from which we are starting. | ||||
| 
 | ||||
|     Returns: | ||||
|       string: relative path to `target`. | ||||
|     """ | ||||
|     t_pieces = target.split("/") | ||||
|     r_pieces = relative_to.split("/") | ||||
|     common_part_len = 0 | ||||
| 
 | ||||
|     for tp, rp in zip(t_pieces, r_pieces): | ||||
|         if tp == rp: | ||||
|             common_part_len += 1 | ||||
|         else: | ||||
|             break | ||||
| 
 | ||||
|     result = [".."] * (len(r_pieces) - common_part_len) | ||||
|     result += t_pieces[common_part_len:] | ||||
| 
 | ||||
|     return "/".join(result) | ||||
| 
 | ||||
| def ln(hs, target, link, extra_inputs = depset()): | ||||
|     """Create a symlink to target. | ||||
| 
 | ||||
|     Args: | ||||
|       hs: Haskell context. | ||||
|       extra_inputs: extra phony dependencies of symlink. | ||||
| 
 | ||||
|     Returns: | ||||
|       None | ||||
|     """ | ||||
|     relative_target = _truly_relativize(target.path, link.dirname) | ||||
|     hs.actions.run_shell( | ||||
|         inputs = depset([target], transitive = [extra_inputs]), | ||||
|         outputs = [link], | ||||
|         mnemonic = "Symlink", | ||||
|         command = "ln -s {target} {link}".format( | ||||
|             target = relative_target, | ||||
|             link = link.path, | ||||
|         ), | ||||
|         use_default_shell_env = True, | ||||
|     ) | ||||
| 
 | ||||
| def link_forest(ctx, srcs, basePath = ".", **kwargs): | ||||
|     """Write a symlink to each file in `srcs` into a destination directory | ||||
|     defined using the same arguments as `ctx.actions.declare_directory`""" | ||||
|     local_files = [] | ||||
|     for src in srcs.to_list(): | ||||
|         dest = ctx.actions.declare_file( | ||||
|             paths.join(basePath, src.basename), | ||||
|             **kwargs | ||||
|         ) | ||||
|         local_files.append(dest) | ||||
|         ln(ctx, src, dest) | ||||
|     return local_files | ||||
| 
 | ||||
| def copy_all(ctx, srcs, dest): | ||||
|     """Copy all the files in `srcs` into `dest`""" | ||||
|     if list(srcs.to_list()) == []: | ||||
|         ctx.actions.run_shell( | ||||
|             command = "mkdir -p {dest}".format(dest = dest.path), | ||||
|             outputs = [dest], | ||||
|         ) | ||||
|     else: | ||||
|         args = ctx.actions.args() | ||||
|         args.add_all(srcs) | ||||
|         ctx.actions.run_shell( | ||||
|             inputs = depset(srcs), | ||||
|             outputs = [dest], | ||||
|             mnemonic = "Copy", | ||||
|             command = "mkdir -p {dest} && cp -L -R \"$@\" {dest}".format(dest = dest.path), | ||||
|             arguments = [args], | ||||
|         ) | ||||
| 
 | ||||
| def parse_pattern(ctx, pattern_str): | ||||
|     """Parses a string label pattern. | ||||
| 
 | ||||
|     Args: | ||||
|       ctx: Standard Bazel Rule context. | ||||
| 
 | ||||
|       pattern_str: The pattern to parse. | ||||
|         Patterns are absolute labels in the local workspace. E.g. | ||||
|         `//some/package:some_target`. The following wild-cards are allowed: | ||||
|         `...`, `:all`, and `:*`. Also the `//some/package` shortcut is allowed. | ||||
| 
 | ||||
|     Returns: | ||||
|       A struct of | ||||
|         package: A list of package path components. May end on the wildcard `...`. | ||||
|         target: The target name. None if the package ends on `...`. May be one | ||||
|           of the wildcards `all` or `*`. | ||||
| 
 | ||||
|     NOTE: it would be better if Bazel itself exposed this functionality to Starlark. | ||||
| 
 | ||||
|     Any feature using this function should be marked as experimental, until the | ||||
|     resolution of https://github.com/bazelbuild/bazel/issues/7763. | ||||
|     """ | ||||
| 
 | ||||
|     # We only load targets in the local workspace anyway. So, it's never | ||||
|     # necessary to specify a workspace. Therefore, we don't allow it. | ||||
|     if pattern_str.startswith("@"): | ||||
|         fail("Invalid haskell_repl pattern. Patterns may not specify a workspace. They only apply to the current workspace") | ||||
| 
 | ||||
|     # To keep things simple, all patterns have to be absolute. | ||||
|     if not pattern_str.startswith("//"): | ||||
|         if not pattern_str.startswith(":"): | ||||
|             fail("Invalid haskell_repl pattern. Patterns must start with either '//' or ':'.") | ||||
| 
 | ||||
|         # if the pattern string doesn't start with a package (it starts with :, e.g. :two), | ||||
|         # then we prepend the contextual package | ||||
|         pattern_str = "//{package}{target}".format(package = ctx.label.package, target = pattern_str) | ||||
| 
 | ||||
|     # Separate package and target (if present). | ||||
|     package_target = pattern_str[2:].split(":", maxsplit = 2) | ||||
|     package_str = package_target[0] | ||||
|     target_str = None | ||||
|     if len(package_target) == 2: | ||||
|         target_str = package_target[1] | ||||
| 
 | ||||
|     # Parse package pattern. | ||||
|     package = [] | ||||
|     dotdotdot = False  # ... has to be last component in the pattern. | ||||
|     for s in package_str.split("/"): | ||||
|         if dotdotdot: | ||||
|             fail("Invalid haskell_repl pattern. ... has to appear at the end.") | ||||
|         if s == "...": | ||||
|             dotdotdot = True | ||||
|         package.append(s) | ||||
| 
 | ||||
|     # Parse target pattern. | ||||
|     if dotdotdot: | ||||
|         if target_str != None: | ||||
|             fail("Invalid haskell_repl pattern. ... has to appear at the end.") | ||||
|     elif target_str == None: | ||||
|         if len(package) > 0 and package[-1] != "": | ||||
|             target_str = package[-1] | ||||
|         else: | ||||
|             fail("Invalid haskell_repl pattern. The empty string is not a valid target.") | ||||
| 
 | ||||
|     return struct( | ||||
|         package = package, | ||||
|         target = target_str, | ||||
|     ) | ||||
| 
 | ||||
| def match_label(patterns, label): | ||||
|     """Whether the given local workspace label matches any of the patterns. | ||||
| 
 | ||||
|     Args: | ||||
|       patterns: A list of parsed patterns to match the label against. | ||||
|         Apply `parse_pattern` before passing patterns into this function. | ||||
|       label: Match this label against the patterns. | ||||
| 
 | ||||
|     Returns: | ||||
|       A boolean. True if the label is in the local workspace and matches any of | ||||
|       the given patterns. False otherwise. | ||||
| 
 | ||||
|     NOTE: it would be better if Bazel itself exposed this functionality to Starlark. | ||||
| 
 | ||||
|     Any feature using this function should be marked as experimental, until the | ||||
|     resolution of https://github.com/bazelbuild/bazel/issues/7763. | ||||
|     """ | ||||
| 
 | ||||
|     # Only local workspace labels can match. | ||||
|     # Despite the docs saying otherwise, labels don't have a workspace_name | ||||
|     # attribute. So, we use the workspace_root. If it's empty, the target is in | ||||
|     # the local workspace. Otherwise, it's an external target. | ||||
|     if label.workspace_root != "": | ||||
|         return False | ||||
| 
 | ||||
|     package = label.package.split("/") | ||||
|     target = label.name | ||||
| 
 | ||||
|     # Match package components. | ||||
|     for i in range(min(len(patterns.package), len(package))): | ||||
|         if patterns.package[i] == "...": | ||||
|             return True | ||||
|         elif patterns.package[i] != package[i]: | ||||
|             return False | ||||
| 
 | ||||
|     # If no wild-card or mismatch was encountered, the lengths must match. | ||||
|     # Otherwise, the label's package is not covered. | ||||
|     if len(patterns.package) != len(package): | ||||
|         return False | ||||
| 
 | ||||
|     # Match target. | ||||
|     if patterns.target == "all" or patterns.target == "*": | ||||
|         return True | ||||
|     else: | ||||
|         return patterns.target == target | ||||
							
								
								
									
										67
									
								
								third_party/bazel/rules_haskell/haskell/private/pkg_id.bzl
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								third_party/bazel/rules_haskell/haskell/private/pkg_id.bzl
									
										
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,67 @@ | |||
| """Package identifiers""" | ||||
| 
 | ||||
| load(":private/mode.bzl", "is_profiling_enabled") | ||||
| load("@bazel_skylib//lib:paths.bzl", "paths") | ||||
| 
 | ||||
| def _zencode(s): | ||||
|     """Z-escape special characters to make a valid GHC package identifier. | ||||
| 
 | ||||
|     Args: | ||||
|       s: string | ||||
|     """ | ||||
|     return s.replace("Z", "ZZ").replace("_", "ZU").replace("/", "ZS") | ||||
| 
 | ||||
| def _to_string(my_pkg_id): | ||||
|     """Get a globally unique package identifier. | ||||
| 
 | ||||
|     The identifier is required to be unique for each Haskell rule. | ||||
|     It includes the Bazel package and the name of this component. | ||||
|     We can't use just the latter because then two components with | ||||
|     the same names in different packages would clash. | ||||
|     """ | ||||
|     return _zencode( | ||||
|         paths.join( | ||||
|             my_pkg_id.label.workspace_root, | ||||
|             my_pkg_id.label.package, | ||||
|             my_pkg_id.name, | ||||
|         ), | ||||
|     ) | ||||
| 
 | ||||
| def _new(label, version = None): | ||||
|     """Create a new package identifier. | ||||
| 
 | ||||
|     Package identifiers should be globally unique. This is why we use | ||||
|     a label to identify them. | ||||
| 
 | ||||
|     Args: | ||||
|       label: The label of the rule declaring the package. | ||||
|       version: an optional version annotation. | ||||
| 
 | ||||
|     Returns: | ||||
|       string: GHC package ID to use. | ||||
| 
 | ||||
|     """ | ||||
|     return struct( | ||||
|         label = label, | ||||
|         name = label.name.replace("_", "-"), | ||||
|         version = version, | ||||
|     ) | ||||
| 
 | ||||
| def _library_name(hs, my_pkg_id, prof_suffix = False): | ||||
|     """Get library name. | ||||
| 
 | ||||
|     Args: | ||||
|       hs: Haskell context. | ||||
|       my_pkg_id: pkg_id struct. | ||||
|       prof_suffix: whether to automatically add profiling suffix. | ||||
|     """ | ||||
|     library_name = "HS" + _to_string(my_pkg_id) | ||||
|     if is_profiling_enabled(hs) and prof_suffix: | ||||
|         library_name += "_p" | ||||
|     return library_name | ||||
| 
 | ||||
| pkg_id = struct( | ||||
|     new = _new, | ||||
|     to_string = _to_string, | ||||
|     library_name = _library_name, | ||||
| ) | ||||
							
								
								
									
										150
									
								
								third_party/bazel/rules_haskell/haskell/private/set.bzl
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										150
									
								
								third_party/bazel/rules_haskell/haskell/private/set.bzl
									
										
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,150 @@ | |||
| """Immutable sets that support efficient merging, traversal, and membership | ||||
| check. | ||||
| """ | ||||
| 
 | ||||
| def _empty(): | ||||
|     """Create an empty set. | ||||
| 
 | ||||
|     Returns: | ||||
|       set, new empty set. | ||||
|     """ | ||||
|     return struct(_set_items = dict()) | ||||
| 
 | ||||
| def _singleton(e): | ||||
|     """Create a set with single element `e` inside. | ||||
| 
 | ||||
|     Args: | ||||
|       e: The element to put in the set. | ||||
| 
 | ||||
|     Returns: | ||||
|       set, new set. | ||||
|     """ | ||||
|     r = dict() | ||||
|     r[e] = None | ||||
|     return struct(_set_items = r) | ||||
| 
 | ||||
| def _is_member(s, e): | ||||
|     """Return true if `e` is in the set `s`. | ||||
| 
 | ||||
|     Args: | ||||
|       s: The set to inspect. | ||||
|       e: The element to search for. | ||||
| 
 | ||||
|     Result: | ||||
|       Bool, true if `e` is in `s`, false otherwise. | ||||
|     """ | ||||
|     return e in s._set_items | ||||
| 
 | ||||
| def _insert(s, e): | ||||
|     """Insert an element into the set. | ||||
| 
 | ||||
|     Args: | ||||
|       s: Set to insert new element into. | ||||
|       e: The element to insert. | ||||
| 
 | ||||
|     Result: | ||||
|       A copy of set `s` with `s` element added. | ||||
|     """ | ||||
|     r = dict(s._set_items) | ||||
|     r[e] = None | ||||
|     return struct(_set_items = r) | ||||
| 
 | ||||
| def _mutable_insert(s, e): | ||||
|     """The same as `set.insert`, but modifies the first argument in place. | ||||
| 
 | ||||
|     Args: | ||||
|       s: Set to insert new element into. | ||||
|       e: The element to insert. | ||||
| 
 | ||||
|     Result: | ||||
|       set `s` with `s` element added. | ||||
|     """ | ||||
|     s._set_items[e] = None | ||||
|     return s | ||||
| 
 | ||||
| def _union(s0, s1): | ||||
|     """Return union of two sets. | ||||
| 
 | ||||
|     Args: | ||||
|       s0: One set. | ||||
|       s1: Another set. | ||||
| 
 | ||||
|     Result: | ||||
|       set, union of the two sets. | ||||
|     """ | ||||
|     r = dict(s0._set_items) | ||||
|     r.update(s1._set_items) | ||||
|     return struct(_set_items = r) | ||||
| 
 | ||||
| def _mutable_union(s0, s1): | ||||
|     """Modify set `s0` adding elements from `s1` to it. | ||||
| 
 | ||||
|     Args: | ||||
|       s0: One set. | ||||
|       s1: Another set. | ||||
| 
 | ||||
|     Result: | ||||
|       set, union of the two sets. | ||||
|     """ | ||||
|     s0._set_items.update(s1._set_items) | ||||
|     return s0 | ||||
| 
 | ||||
| def _map(s, f): | ||||
|     """Map elements of given set using a function. | ||||
| 
 | ||||
|     Args: | ||||
|       s: Original set. | ||||
|       f: Function to apply to elements of the set. | ||||
| 
 | ||||
|     Result: | ||||
|       set with elements obtained by application of function `f` to the | ||||
|       elements of `s`. | ||||
|     """ | ||||
|     return struct(_set_items = {f(x): None for x in s._set_items.keys()}) | ||||
| 
 | ||||
| def _from_list(l): | ||||
|     """Create a set containing elements from given list. | ||||
| 
 | ||||
|     Args: | ||||
|       l: List, source of the elements for the new set. | ||||
| 
 | ||||
|     Result: | ||||
|       set containing elements from given list. | ||||
|     """ | ||||
|     return (struct(_set_items = {x: None for x in l})) | ||||
| 
 | ||||
| def _to_list(s): | ||||
|     """Convert set into a list of its elements. | ||||
| 
 | ||||
|     Args: | ||||
|       s: Set to convert. | ||||
| 
 | ||||
|     Returns: | ||||
|       List of elements of the set. | ||||
|     """ | ||||
|     return s._set_items.keys() | ||||
| 
 | ||||
| def _to_depset(s): | ||||
|     """Similar to `set.to_list`, but produces a depset. | ||||
| 
 | ||||
|     Args: | ||||
|       s: Set to convert. | ||||
| 
 | ||||
|     Returns: | ||||
|       Depset of elements from the set. | ||||
|     """ | ||||
|     return depset(_to_list(s)) | ||||
| 
 | ||||
| set = struct( | ||||
|     empty = _empty, | ||||
|     singleton = _singleton, | ||||
|     is_member = _is_member, | ||||
|     insert = _insert, | ||||
|     mutable_insert = _mutable_insert, | ||||
|     union = _union, | ||||
|     mutable_union = _mutable_union, | ||||
|     map = _map, | ||||
|     from_list = _from_list, | ||||
|     to_list = _to_list, | ||||
|     to_depset = _to_depset, | ||||
| ) | ||||
							
								
								
									
										47
									
								
								third_party/bazel/rules_haskell/haskell/private/version_macros.bzl
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								third_party/bazel/rules_haskell/haskell/private/version_macros.bzl
									
										
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,47 @@ | |||
| load(":private/set.bzl", "set") | ||||
| 
 | ||||
| def generate_version_macros(ctx, name, version): | ||||
|     """Generate a version macros header file. | ||||
| 
 | ||||
|     Args: | ||||
|         ctx: Rule context. Needs to define a _version_macros executable attribute. | ||||
|         name: The package name. | ||||
|         version: The package version. | ||||
| 
 | ||||
|     Returns: | ||||
|         Version macros header File. | ||||
|     """ | ||||
|     version_macros_file = ctx.actions.declare_file("{}_version_macros.h".format(name)) | ||||
|     ctx.actions.run_shell( | ||||
|         inputs = [ctx.executable._version_macros], | ||||
|         outputs = [version_macros_file], | ||||
|         command = """ | ||||
|         "$1" "$2" "$3" > "$4" | ||||
|         """, | ||||
|         arguments = [ | ||||
|             ctx.executable._version_macros.path, | ||||
|             name, | ||||
|             version, | ||||
|             version_macros_file.path, | ||||
|         ], | ||||
|     ) | ||||
|     return version_macros_file | ||||
| 
 | ||||
| def version_macro_includes(hs_info): | ||||
|     """Generate a list of version macro header includes. | ||||
| 
 | ||||
|     Args: | ||||
|         hs_info: HaskellInfo provider. | ||||
| 
 | ||||
|     Returns: | ||||
|         (files, flags): | ||||
|         files: Set of version macros header files. | ||||
|         flags: List of C preprocessor flags to include version macros. | ||||
|     """ | ||||
|     files = hs_info.version_macros | ||||
|     flags = [ | ||||
|         f | ||||
|         for include in set.to_list(files) | ||||
|         for f in ["-include", include.path] | ||||
|     ] | ||||
|     return (files, flags) | ||||
							
								
								
									
										101
									
								
								third_party/bazel/rules_haskell/haskell/private/version_macros.py
									
										
									
									
										vendored
									
									
										Executable file
									
								
							
							
						
						
									
										101
									
								
								third_party/bazel/rules_haskell/haskell/private/version_macros.py
									
										
									
									
										vendored
									
									
										Executable file
									
								
							|  | @ -0,0 +1,101 @@ | |||
| #!/usr/bin/env python3 | ||||
| """Generate Cabal version macros. | ||||
| 
 | ||||
| Generates the content of a C header file for the given library name and version | ||||
| and prints it to standard output. | ||||
| """ | ||||
| 
 | ||||
| import argparse | ||||
| 
 | ||||
| 
 | ||||
| def main(): | ||||
|     parser = argparse.ArgumentParser(description=__doc__) | ||||
|     parser.add_argument("name", help="The package name.") | ||||
|     parser.add_argument("version", help="The package version.") | ||||
|     args = parser.parse_args() | ||||
| 
 | ||||
|     print(version_macros(args.name, args.version)) | ||||
| 
 | ||||
| 
 | ||||
| def version_macros(name, version): | ||||
|     """Generate Cabal version macros. | ||||
| 
 | ||||
|     Based on Cabal's version macro generation, see [1]. | ||||
| 
 | ||||
|     [1]: http://hackage.haskell.org/package/Cabal-2.4.1.0/docs/src/Distribution.Simple.Build.Macros.html#generatePackageVersionMacros | ||||
|     """ | ||||
|     (major1, major2, minor) = version_components(version) | ||||
|     escaped_name = cpp_escape_name(name) | ||||
|     return "\n".join([ | ||||
|         # #define VERSION_pkg "1.2.3" | ||||
|         cpp_ifndef_define( | ||||
|             "VERSION_" + escaped_name, | ||||
|             [], | ||||
|             '"{}"'.format(version), | ||||
|         ), | ||||
|         # #define MIN_VERSION_pkg(major1, major2, minor) ... | ||||
|         cpp_ifndef_define( | ||||
|             "MIN_VERSION_" + escaped_name, | ||||
|             ["major1", "major2", "minor"], | ||||
|             " \\\n".join([ | ||||
|                 "(", | ||||
|                 "  (major1) < {} ||".format(major1), | ||||
|                 "  (major1) == {} && (major2) < {} ||".format(major1, major2), | ||||
|                 "  (major1) == {} && (major2) == {} && (minor) <= {} )".format( | ||||
|                     major1, major2, minor), | ||||
|             ])), | ||||
|     ]) | ||||
| 
 | ||||
| 
 | ||||
| def version_components(version): | ||||
|     """Split version string into major1.major2.minor components.""" | ||||
|     components = version.split(".") | ||||
|     num = len(components) | ||||
| 
 | ||||
|     if num < 1: | ||||
|         raise ValueError("version should have at least one component.") | ||||
| 
 | ||||
|     major1 = components[0] | ||||
| 
 | ||||
|     if num >= 2: | ||||
|         major2 = components[1] | ||||
|     else: | ||||
|         major2 = "0" | ||||
| 
 | ||||
|     if num >= 3: | ||||
|         minor = components[2] | ||||
|     else: | ||||
|         minor = "0" | ||||
| 
 | ||||
|     return (major1, major2, minor) | ||||
| 
 | ||||
| 
 | ||||
| def cpp_escape_name(name): | ||||
|     """Escape package name to be CPP macro safe.""" | ||||
|     return name.replace("-", "_") | ||||
| 
 | ||||
| 
 | ||||
| def cpp_define(macro, params, val): | ||||
|     """CPP macro definition, optionally with parameters.""" | ||||
|     return "#define {macro}{params} {val}".format( | ||||
|         macro = macro, | ||||
|         params = "({})".format(",".join(params)) if params else "", | ||||
|         val = val, | ||||
|     ) | ||||
| 
 | ||||
| 
 | ||||
| def cpp_ifndef(macro, body): | ||||
|     """CPP ifndef block.""" | ||||
|     return "#ifndef {macro}\n{body}\n#endif /* {macro} */".format( | ||||
|         macro = macro, | ||||
|         body = body, | ||||
|     ) | ||||
| 
 | ||||
| 
 | ||||
| def cpp_ifndef_define(macro, params, val): | ||||
|     """CPP macro definition, if not previously defined.""" | ||||
|     return cpp_ifndef(macro, cpp_define(macro, params, val)) | ||||
| 
 | ||||
| 
 | ||||
| if __name__ == "__main__": | ||||
|     main() | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue