395 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			395 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| """Support for protocol buffers"""
 | |
| 
 | |
| load(
 | |
|     ":private/haskell_impl.bzl",
 | |
|     _haskell_library_impl = "haskell_library_impl",
 | |
| )
 | |
| load("@bazel_skylib//lib:paths.bzl", "paths")
 | |
| load(
 | |
|     "@io_tweag_rules_haskell//haskell:providers.bzl",
 | |
|     "HaskellInfo",
 | |
|     "HaskellLibraryInfo",
 | |
|     "HaskellProtobufInfo",
 | |
| )
 | |
| 
 | |
| def _capitalize_first_letter(c):
 | |
|     """Capitalize the first letter of the input. Unlike the built-in
 | |
|     `capitalize()` method, doesn't lower-case the other characters. This helps
 | |
|     mimic the behavior of `proto-lens-protoc`, which turns `Foo/Bar/BAZ.proto`
 | |
|     into `Foo/Bar/BAZ.hs` (rather than `Foo/Bar/Baz.hs`).
 | |
| 
 | |
|     Args:
 | |
|       c: A non-empty string word.
 | |
| 
 | |
|     Returns:
 | |
|       The input with the first letter upper-cased.
 | |
|     """
 | |
|     return c[0].capitalize() + c[1:]
 | |
| 
 | |
| def _camel_case(comp):
 | |
|     """Camel-case the input string, preserving any existing capital letters.
 | |
|     """
 | |
| 
 | |
|     # Split on both "-" and "_", matching the behavior of proto-lens-protoc.
 | |
|     # Be sure to ignore any empty segments from input with leading or trailing
 | |
|     # delimiters.
 | |
|     return "".join([
 | |
|         _capitalize_first_letter(c2)
 | |
|         for c1 in comp.split("_")
 | |
|         for c2 in c1.split("-")
 | |
|         if len(c2) > 0
 | |
|     ])
 | |
| 
 | |
| def _proto_lens_output_file(path):
 | |
|     """The output file from `proto-lens-protoc` when run on the given `path`.
 | |
|     """
 | |
| 
 | |
|     path = path[:-len(".proto")]
 | |
|     result = "/".join([_camel_case(p) for p in path.split("/")]) + ".hs"
 | |
| 
 | |
|     return "Proto/" + result
 | |
| 
 | |
| def _proto_lens_fields_file(path):
 | |
|     """The fields file from `proto-lens-protoc` when run on the given `path`.
 | |
|     """
 | |
| 
 | |
|     path = path[:-len(".proto")]
 | |
|     result = "/".join([_camel_case(p) for p in path.split("/")]) + "_Fields.hs"
 | |
| 
 | |
|     return "Proto/" + result
 | |
| 
 | |
| def _proto_path(proto, proto_source_roots):
 | |
|     """A path to the proto file which matches any import statements."""
 | |
|     proto_path = proto.path
 | |
|     for p in proto_source_roots:
 | |
|         if proto_path.startswith(p):
 | |
|             return paths.relativize(proto_path, p)
 | |
| 
 | |
|     return paths.relativize(
 | |
|         proto_path,
 | |
|         paths.join(proto.root.path, proto.owner.workspace_root),
 | |
|     )
 | |
| 
 | |
| def _haskell_proto_aspect_impl(target, ctx):
 | |
|     pb = ctx.toolchains["@io_tweag_rules_haskell//protobuf:toolchain"].tools
 | |
| 
 | |
|     args = ctx.actions.args()
 | |
| 
 | |
|     src_prefix = paths.join(
 | |
|         ctx.label.workspace_root,
 | |
|         ctx.label.package,
 | |
|     )
 | |
| 
 | |
|     args.add("--plugin=protoc-gen-haskell=" + pb.plugin.path)
 | |
| 
 | |
|     hs_files = []
 | |
|     inputs = []
 | |
| 
 | |
|     direct_proto_paths = [target.proto.proto_source_root]
 | |
|     transitive_proto_paths = target.proto.transitive_proto_path
 | |
| 
 | |
|     args.add_all([
 | |
|         "-I{0}={1}".format(_proto_path(s, transitive_proto_paths), s.path)
 | |
|         for s in target.proto.transitive_sources.to_list()
 | |
|     ])
 | |
| 
 | |
|     inputs.extend(target.proto.transitive_sources.to_list())
 | |
| 
 | |
|     for src in target.proto.direct_sources:
 | |
|         inputs.append(src)
 | |
| 
 | |
|         # As with the native rules, require the .proto file to be in the same
 | |
|         # Bazel package as the proto_library rule. This allows us to put the
 | |
|         # output .hs file next to the input .proto file. Unfortunately Skylark
 | |
|         # doesn't let us check the package of the file directly, so instead we
 | |
|         # just look at its short_path and rely on the proto_library rule itself
 | |
|         # to check for consistency. We use the file's path rather than its
 | |
|         # dirname/basename in case it's in a subdirectory; for example, if the
 | |
|         # proto_library rule is in "foo/BUILD" but the .proto file is
 | |
|         # "foo/bar/baz.proto".
 | |
| 
 | |
|         if not src.path.startswith(paths.join(src.root.path, src_prefix)):
 | |
|             fail("Mismatch between rule context " + str(ctx.label.package) +
 | |
|                  " and source file " + src.short_path)
 | |
|         if src.basename[-6:] != ".proto":
 | |
|             fail("bad extension for proto file " + src)
 | |
| 
 | |
|         args.add(src.path)
 | |
|         hs_files.append(ctx.actions.declare_file(
 | |
|             _proto_lens_output_file(
 | |
|                 _proto_path(src, direct_proto_paths),
 | |
|             ),
 | |
|         ))
 | |
|         hs_files.append(ctx.actions.declare_file(
 | |
|             _proto_lens_fields_file(
 | |
|                 _proto_path(src, direct_proto_paths),
 | |
|             ),
 | |
|         ))
 | |
| 
 | |
|     args.add_all([
 | |
|         "--proto_path=" + target.proto.proto_source_root,
 | |
|         "--haskell_out=no-runtime:" + paths.join(
 | |
|             hs_files[0].root.path,
 | |
|             src_prefix,
 | |
|         ),
 | |
|     ])
 | |
| 
 | |
|     ctx.actions.run(
 | |
|         inputs = depset([pb.protoc, pb.plugin] + inputs),
 | |
|         outputs = hs_files,
 | |
|         mnemonic = "HaskellProtoc",
 | |
|         executable = pb.protoc,
 | |
|         arguments = [args],
 | |
|     )
 | |
| 
 | |
|     patched_attrs = {
 | |
|         "compiler_flags": [],
 | |
|         "src_strip_prefix": "",
 | |
|         "repl_interpreted": True,
 | |
|         "repl_ghci_args": [],
 | |
|         "version": "",
 | |
|         "linkstatic": False,
 | |
|         "_ghci_script": ctx.attr._ghci_script,
 | |
|         "_ghci_repl_wrapper": ctx.attr._ghci_repl_wrapper,
 | |
|         "hidden_modules": [],
 | |
|         "exports": {},
 | |
|         "name": "proto-autogen-" + ctx.rule.attr.name,
 | |
|         "srcs": hs_files,
 | |
|         "deps": ctx.rule.attr.deps +
 | |
|                 ctx.toolchains["@io_tweag_rules_haskell//protobuf:toolchain"].deps,
 | |
|         "prebuilt_dependencies": ctx.toolchains["@io_tweag_rules_haskell//protobuf:toolchain"].prebuilt_deps,
 | |
|         "plugins": [],
 | |
|         "_cc_toolchain": ctx.attr._cc_toolchain,
 | |
|     }
 | |
| 
 | |
|     patched_ctx = struct(
 | |
|         actions = ctx.actions,
 | |
|         attr = struct(**patched_attrs),
 | |
|         bin_dir = ctx.bin_dir,
 | |
|         disabled_features = ctx.rule.attr.features,
 | |
|         executable = struct(
 | |
|             _ls_modules = ctx.executable._ls_modules,
 | |
|         ),
 | |
|         # Necessary for CC interop (see cc.bzl).
 | |
|         features = ctx.rule.attr.features,
 | |
|         file = ctx.file,
 | |
|         files = struct(
 | |
|             srcs = hs_files,
 | |
|             _cc_toolchain = ctx.files._cc_toolchain,
 | |
|             extra_srcs = depset(),
 | |
|         ),
 | |
|         genfiles_dir = ctx.genfiles_dir,
 | |
|         label = ctx.label,
 | |
|         toolchains = ctx.toolchains,
 | |
|         var = ctx.var,
 | |
|     )
 | |
| 
 | |
|     # TODO this pattern match is very brittle. Let's not do this. The
 | |
|     # order should match the order in the return value expression in
 | |
|     # haskell_library_impl().
 | |
|     [hs_info, cc_info, coverage_info, default_info, library_info] = _haskell_library_impl(patched_ctx)
 | |
| 
 | |
|     return [
 | |
|         cc_info,  # CcInfo
 | |
|         hs_info,  # HaskellInfo
 | |
|         library_info,  # HaskellLibraryInfo
 | |
|         # We can't return DefaultInfo here because target already provides that.
 | |
|         HaskellProtobufInfo(files = default_info.files),
 | |
|     ]
 | |
| 
 | |
| _haskell_proto_aspect = aspect(
 | |
|     _haskell_proto_aspect_impl,
 | |
|     attr_aspects = ["deps"],
 | |
|     attrs = {
 | |
|         "_ghci_script": attr.label(
 | |
|             allow_single_file = True,
 | |
|             default = Label("@io_tweag_rules_haskell//haskell:assets/ghci_script"),
 | |
|         ),
 | |
|         "_ghci_repl_wrapper": attr.label(
 | |
|             allow_single_file = True,
 | |
|             default = Label("@io_tweag_rules_haskell//haskell:private/ghci_repl_wrapper.sh"),
 | |
|         ),
 | |
|         "_ls_modules": attr.label(
 | |
|             executable = True,
 | |
|             cfg = "host",
 | |
|             default = Label("@io_tweag_rules_haskell//haskell:ls_modules"),
 | |
|         ),
 | |
|         "_cc_toolchain": attr.label(
 | |
|             default = Label("@bazel_tools//tools/cpp:current_cc_toolchain"),
 | |
|         ),
 | |
|     },
 | |
|     toolchains = [
 | |
|         "@io_tweag_rules_haskell//haskell:toolchain",
 | |
|         "@io_tweag_rules_haskell//protobuf:toolchain",
 | |
|     ],
 | |
| )
 | |
| 
 | |
| def _haskell_proto_library_impl(ctx):
 | |
|     dep = ctx.attr.deps[0]  # FIXME
 | |
|     return [
 | |
|         dep[CcInfo],
 | |
|         dep[HaskellInfo],
 | |
|         dep[HaskellLibraryInfo],
 | |
|         DefaultInfo(files = dep[HaskellProtobufInfo].files),
 | |
|     ]
 | |
| 
 | |
| haskell_proto_library = rule(
 | |
|     _haskell_proto_library_impl,
 | |
|     attrs = {
 | |
|         "deps": attr.label_list(
 | |
|             mandatory = True,
 | |
|             allow_files = False,
 | |
|             aspects = [_haskell_proto_aspect],
 | |
|             doc = "List of `proto_library` targets to use for generation.",
 | |
|         ),
 | |
|     },
 | |
|     toolchains = [
 | |
|         "@io_tweag_rules_haskell//haskell:toolchain",
 | |
|         "@io_tweag_rules_haskell//protobuf:toolchain",
 | |
|     ],
 | |
| )
 | |
| 
 | |
| """Generate Haskell library allowing to use protobuf definitions with help
 | |
| of [`proto-lens`](https://github.com/google/proto-lens#readme).
 | |
| 
 | |
| Example:
 | |
|   ```bzl
 | |
|   proto_library(
 | |
|     name = "foo_proto",
 | |
|     srcs = ["foo.proto"],
 | |
|   )
 | |
| 
 | |
|   haskell_proto_library(
 | |
|     name = "foo_haskell_proto",
 | |
|     deps = [":foo_proto"],
 | |
|   )
 | |
|   ```
 | |
| 
 | |
| `haskell_proto_library` targets require `haskell_proto_toolchain` to be
 | |
| registered.
 | |
| """
 | |
| 
 | |
| def _protobuf_toolchain_impl(ctx):
 | |
|     if ctx.attr.prebuilt_deps:
 | |
|         print("""The attribute 'prebuilt_deps' has been deprecated,
 | |
| use the 'deps' attribute instead.
 | |
| """)
 | |
| 
 | |
|     return [
 | |
|         platform_common.ToolchainInfo(
 | |
|             name = ctx.label.name,
 | |
|             tools = struct(
 | |
|                 plugin = ctx.executable.plugin,
 | |
|                 protoc = ctx.executable.protoc,
 | |
|             ),
 | |
|             deps = ctx.attr.deps,
 | |
|             prebuilt_deps = ctx.attr.prebuilt_deps,
 | |
|         ),
 | |
|     ]
 | |
| 
 | |
| _protobuf_toolchain = rule(
 | |
|     _protobuf_toolchain_impl,
 | |
|     attrs = {
 | |
|         "protoc": attr.label(
 | |
|             executable = True,
 | |
|             cfg = "host",
 | |
|             allow_single_file = True,
 | |
|             mandatory = True,
 | |
|             doc = "protoc compiler",
 | |
|         ),
 | |
|         "plugin": attr.label(
 | |
|             executable = True,
 | |
|             cfg = "host",
 | |
|             allow_single_file = True,
 | |
|             mandatory = True,
 | |
|             doc = "proto-lens-protoc plugin for protoc",
 | |
|         ),
 | |
|         "deps": attr.label_list(
 | |
|             doc = "List of other Haskell libraries to be linked to protobuf libraries.",
 | |
|         ),
 | |
|         "prebuilt_deps": attr.string_list(
 | |
|             doc = "Non-Bazel supplied Cabal dependencies for protobuf libraries.",
 | |
|         ),
 | |
|     },
 | |
| )
 | |
| 
 | |
| def haskell_proto_toolchain(
 | |
|         name,
 | |
|         plugin,
 | |
|         deps = [],
 | |
|         prebuilt_deps = [],
 | |
|         protoc = Label("@com_google_protobuf//:protoc"),
 | |
|         **kwargs):
 | |
|     """Declare a Haskell protobuf toolchain.
 | |
| 
 | |
|     You need at least one of these declared somewhere in your `BUILD` files
 | |
|     for the `haskell_proto_library` rules to work. Once declared, you then
 | |
|     need to *register* the toolchain using `register_toolchains` in your
 | |
|     `WORKSPACE` file (see example below).
 | |
| 
 | |
|     Example:
 | |
| 
 | |
|       In a `BUILD` file:
 | |
| 
 | |
|       ```bzl
 | |
|       haskell_proto_toolchain(
 | |
|         name = "protobuf-toolchain",
 | |
|         protoc = "@com_google_protobuf//:protoc",
 | |
|         plugin = "@hackage-proto-lens-protoc//:bin/proto-lens-protoc",
 | |
|         prebuilt_deps = [
 | |
|           "base",
 | |
|           "bytestring",
 | |
|           "containers",
 | |
|           "data-default-class",
 | |
|           "lens-family",
 | |
|           "lens-labels",
 | |
|           "proto-lens",
 | |
|           "text",
 | |
|         ],
 | |
|       )
 | |
|       ```
 | |
| 
 | |
|       The `prebuilt_deps` and `deps` arguments allow to specify Haskell
 | |
|       libraries to use to compile the auto-generated source files.
 | |
| 
 | |
|       In `WORKSPACE` you could have something like this:
 | |
| 
 | |
|       ```bzl
 | |
|       http_archive(
 | |
|         name = "com_google_protobuf",
 | |
|         sha256 = "cef7f1b5a7c5fba672bec2a319246e8feba471f04dcebfe362d55930ee7c1c30",
 | |
|         strip_prefix = "protobuf-3.5.0",
 | |
|         urls = ["https://github.com/google/protobuf/archive/v3.5.0.zip"],
 | |
|       )
 | |
| 
 | |
|       nixpkgs_package(
 | |
|         name = "protoc_gen_haskell",
 | |
|         repository = "@nixpkgs",
 | |
|         attribute_path = "haskell.packages.ghc822.proto-lens-protoc
 | |
|       )
 | |
| 
 | |
|       register_toolchains(
 | |
|         "//tests:ghc", # assuming you called your Haskell toolchain "ghc"
 | |
|         "//tests:protobuf-toolchain",
 | |
|       )
 | |
|       ```
 | |
|     """
 | |
|     impl_name = name + "-impl"
 | |
|     _protobuf_toolchain(
 | |
|         name = impl_name,
 | |
|         plugin = plugin,
 | |
|         deps = deps,
 | |
|         prebuilt_deps = prebuilt_deps,
 | |
|         protoc = protoc,
 | |
|         visibility = ["//visibility:public"],
 | |
|         **kwargs
 | |
|     )
 | |
| 
 | |
|     native.toolchain(
 | |
|         name = name,
 | |
|         toolchain_type = "@io_tweag_rules_haskell//protobuf:toolchain",
 | |
|         toolchain = ":" + impl_name,
 | |
|         exec_compatible_with = [
 | |
|             "@bazel_tools//platforms:x86_64",
 | |
|         ],
 | |
|     )
 |