• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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"""
9Definition 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
15def _uniq(iterable):
16    unique_elements = {element: None for element in iterable}
17    return list(unique_elements.keys())
18
19def _join(*path):
20    return "/".join([p for p in path if p != ""])
21
22def _empty_to_dot(path):
23    return path if path else "."
24
25def _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
40def _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
138ProtoInfo, _ = 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)
187