|
| 1 | +# Protocol Buffers - Google's data interchange format |
| 2 | +# Copyright 2024 Google Inc. All rights reserved. |
| 3 | +# |
| 4 | +# Use of this source code is governed by a BSD-style |
| 5 | +# license that can be found in the LICENSE file or at |
| 6 | +# https://developers.google.com/open-source/licenses/bsd |
| 7 | +# |
| 8 | +""" |
| 9 | +Definition of ProtoInfo provider. |
| 10 | +""" |
| 11 | + |
| 12 | +_warning = """ Don't use this field. It's intended for internal use and will be changed or removed |
| 13 | + without warning.""" |
| 14 | + |
| 15 | +def _uniq(iterable): |
| 16 | + unique_elements = {element: None for element in iterable} |
| 17 | + return list(unique_elements.keys()) |
| 18 | + |
| 19 | +def _join(*path): |
| 20 | + return "/".join([p for p in path if p != ""]) |
| 21 | + |
| 22 | +def _empty_to_dot(path): |
| 23 | + return path if path else "." |
| 24 | + |
| 25 | +def _from_root(root, repo, relpath): |
| 26 | + """Constructs an exec path from root to relpath""" |
| 27 | + if not root: |
| 28 | + # `relpath` is a directory with an input source file, the exec path is one of: |
| 29 | + # - when in main repo: `package/path` |
| 30 | + # - when in a external repository: `external/repo/package/path` |
| 31 | + # - with sibling layout: `../repo/package/path` |
| 32 | + return _join(repo, relpath) |
| 33 | + else: |
| 34 | + # `relpath` is a directory with a generated file or an output directory: |
| 35 | + # - when in main repo: `{root}/package/path` |
| 36 | + # - when in an external repository: `{root}/external/repo/package/path` |
| 37 | + # - with sibling layout: `{root}/package/path` |
| 38 | + return _join(root, "" if repo.startswith("../") else repo, relpath) |
| 39 | + |
| 40 | +def _create_proto_info(*, srcs, deps, descriptor_set, proto_path = "", workspace_root = "", bin_dir = None, allow_exports = None): |
| 41 | + """Constructs ProtoInfo. |
| 42 | +
|
| 43 | + Args: |
| 44 | + srcs: ([File]) List of .proto files (possibly under _virtual path) |
| 45 | + deps: ([ProtoInfo]) List of dependencies |
| 46 | + descriptor_set: (File) Descriptor set for this Proto |
| 47 | + proto_path: (str) Path that should be stripped from files in srcs. When |
| 48 | + stripping is needed, the files should be symlinked into `_virtual_imports/target_name` |
| 49 | + directory. Only such paths are accepted. |
| 50 | + workspace_root: (str) Set to ctx.workspace_root if this is not the main repository. |
| 51 | + bin_dir: (str) Set to ctx.bin_dir if _virtual_imports are used. |
| 52 | + allow_exports: (Target) The packages where this proto_library can be exported. |
| 53 | +
|
| 54 | + Returns: |
| 55 | + (ProtoInfo) |
| 56 | + """ |
| 57 | + |
| 58 | + # Validate parameters |
| 59 | + src_prefix = _join(workspace_root.replace("external/", "../"), proto_path) |
| 60 | + for src in srcs: |
| 61 | + if type(src) != "File": |
| 62 | + fail("srcs parameter expects a list of Files") |
| 63 | + if src.owner.workspace_root != workspace_root: |
| 64 | + fail("srcs parameter expects all files to have the same workspace_root: ", workspace_root) |
| 65 | + if not src.short_path.startswith(src_prefix): |
| 66 | + fail("srcs parameter expects all files start with %s" % src_prefix) |
| 67 | + if type(descriptor_set) != "File": |
| 68 | + fail("descriptor_set parameter expected to be a File") |
| 69 | + if proto_path: |
| 70 | + if "_virtual_imports/" not in proto_path: |
| 71 | + fail("proto_path needs to contain '_virtual_imports' directory") |
| 72 | + if proto_path.split("/")[-2] != "_virtual_imports": |
| 73 | + fail("proto_path needs to be formed like '_virtual_imports/target_name'") |
| 74 | + if not bin_dir: |
| 75 | + fail("bin_dir parameter should be set when _virtual_imports are used") |
| 76 | + |
| 77 | + direct_proto_sources = srcs |
| 78 | + transitive_proto_sources = depset( |
| 79 | + direct = direct_proto_sources, |
| 80 | + transitive = [dep._transitive_proto_sources for dep in deps], |
| 81 | + order = "preorder", |
| 82 | + ) |
| 83 | + transitive_sources = depset( |
| 84 | + direct = srcs, |
| 85 | + transitive = [dep.transitive_sources for dep in deps], |
| 86 | + order = "preorder", |
| 87 | + ) |
| 88 | + |
| 89 | + # There can be up more than 1 direct proto_paths, for example when there's |
| 90 | + # a generated and non-generated .proto file in srcs |
| 91 | + root_paths = _uniq([src.root.path for src in srcs]) |
| 92 | + transitive_proto_path = depset( |
| 93 | + direct = [_empty_to_dot(_from_root(root, workspace_root, proto_path)) for root in root_paths], |
| 94 | + transitive = [dep.transitive_proto_path for dep in deps], |
| 95 | + ) |
| 96 | + |
| 97 | + if srcs: |
| 98 | + check_deps_sources = depset(direct = srcs) |
| 99 | + else: |
| 100 | + check_deps_sources = depset(transitive = [dep.check_deps_sources for dep in deps]) |
| 101 | + |
| 102 | + transitive_descriptor_sets = depset( |
| 103 | + direct = [descriptor_set], |
| 104 | + transitive = [dep.transitive_descriptor_sets for dep in deps], |
| 105 | + ) |
| 106 | + |
| 107 | + # Layering checks. |
| 108 | + if srcs: |
| 109 | + exported_sources = depset(direct = direct_proto_sources) |
| 110 | + else: |
| 111 | + exported_sources = depset(transitive = [dep._exported_sources for dep in deps]) |
| 112 | + |
| 113 | + if "_virtual_imports/" in proto_path: |
| 114 | + #TODO: remove bin_dir from proto_source_root (when users assuming it's there are migrated) |
| 115 | + proto_source_root = _empty_to_dot(_from_root(bin_dir, workspace_root, proto_path)) |
| 116 | + elif workspace_root.startswith("../"): |
| 117 | + proto_source_root = proto_path |
| 118 | + else: |
| 119 | + proto_source_root = _empty_to_dot(_join(workspace_root, proto_path)) |
| 120 | + |
| 121 | + proto_info = dict( |
| 122 | + direct_sources = srcs, |
| 123 | + transitive_sources = transitive_sources, |
| 124 | + direct_descriptor_set = descriptor_set, |
| 125 | + transitive_descriptor_sets = transitive_descriptor_sets, |
| 126 | + proto_source_root = proto_source_root, |
| 127 | + transitive_proto_path = transitive_proto_path, |
| 128 | + check_deps_sources = check_deps_sources, |
| 129 | + transitive_imports = transitive_sources, |
| 130 | + _direct_proto_sources = direct_proto_sources, |
| 131 | + _transitive_proto_sources = transitive_proto_sources, |
| 132 | + _exported_sources = exported_sources, |
| 133 | + ) |
| 134 | + if allow_exports: |
| 135 | + proto_info["allow_exports"] = allow_exports |
| 136 | + return proto_info |
| 137 | + |
| 138 | +ProtoInfo, _ = provider( |
| 139 | + doc = "Encapsulates information provided by a `proto_library.`", |
| 140 | + fields = { |
| 141 | + "direct_sources": "(list[File]) The `.proto` source files from the `srcs` attribute.", |
| 142 | + "transitive_sources": """(depset[File]) The `.proto` source files from this rule and all |
| 143 | + its dependent protocol buffer rules.""", |
| 144 | + "direct_descriptor_set": """(File) The descriptor set of the direct sources. If no srcs, |
| 145 | + contains an empty file.""", |
| 146 | + "transitive_descriptor_sets": """(depset[File]) A set of descriptor set files of all |
| 147 | + dependent `proto_library` rules, and this one's. This is not the same as passing |
| 148 | + --include_imports to proto-compiler. Will be empty if no dependencies.""", |
| 149 | + "proto_source_root": """(str) The directory relative to which the `.proto` files defined in |
| 150 | + the `proto_library` are defined. For example, if this is `a/b` and the rule has the |
| 151 | + file `a/b/c/d.proto` as a source, that source file would be imported as |
| 152 | + `import c/d.proto` |
| 153 | +
|
| 154 | + In principle, the `proto_source_root` directory itself should always |
| 155 | + be relative to the output directory (`ctx.bin_dir`). |
| 156 | +
|
| 157 | + This is at the moment not true for `proto_libraries` using (additional and/or strip) |
| 158 | + import prefixes. `proto_source_root` is in this case prefixed with the output |
| 159 | + directory. For example, the value is similar to |
| 160 | + `bazel-out/k8-fastbuild/bin/a/_virtual_includes/b` for an input file in |
| 161 | + `a/_virtual_includes/b/c.proto` that should be imported as `c.proto`. |
| 162 | +
|
| 163 | + When using the value please account for both cases in a general way. |
| 164 | + That is assume the value is either prefixed with the output directory or not. |
| 165 | + This will make it possible to fix `proto_library` in the future. |
| 166 | + """, |
| 167 | + "transitive_proto_path": """(depset(str) A set of `proto_source_root`s collected from the |
| 168 | + transitive closure of this rule.""", |
| 169 | + "check_deps_sources": """(depset[File]) The `.proto` sources from the 'srcs' attribute. |
| 170 | + If the library is a proxy library that has no sources, it contains the |
| 171 | + `check_deps_sources` from this library's direct deps.""", |
| 172 | + "allow_exports": """(Target) The packages where this proto_library can be exported.""", |
| 173 | + |
| 174 | + # Deprecated fields: |
| 175 | + "transitive_imports": """(depset[File]) Deprecated: use `transitive_sources` instead.""", |
| 176 | + |
| 177 | + # Internal fields: |
| 178 | + "_direct_proto_sources": """(list[File]) The `ProtoSourceInfo`s from the `srcs` |
| 179 | + attribute.""" + _warning, |
| 180 | + "_transitive_proto_sources": """(depset[File]) The `ProtoSourceInfo`s from this |
| 181 | + rule and all its dependent protocol buffer rules.""" + _warning, |
| 182 | + "_exported_sources": """(depset[File]) A set of `ProtoSourceInfo`s that may be |
| 183 | + imported by another `proto_library` depending on this one.""" + _warning, |
| 184 | + }, |
| 185 | + init = _create_proto_info, |
| 186 | +) |
0 commit comments