Skip to content

Commit 6c6c196

Browse files
comiuscopybara-github
authored andcommitted
Fix paths for sibling repository setup and generated .proto files
Implement additional tests for protos across different repos with and without strip prefixes. Verify cc_proto_library and java_proto_library work over this setup without warnings. Choose cc_proto_library, because it generates files per .proto file and java_proto_library, because it generates a single file (jar) per proto_library. Address problems with --experimental_sibling_repository_layout: `-I../a.proto=<path>` never worked. Correct to `-Ia.proto=<path>`. Address problems with generated files in Bazel repositories when generate_protos_in_virtual_imports is False, which were hidden because the flag defaults to true. Fix incorrect and/or missing paths in --proto_path argument. Make it explicit that ProtoInfo.proto_source_root is in some cases returning a path with bindir/genfilesdir and in other cases without. PiperOrigin-RevId: 531414364 Change-Id: I1edf734632c58f50ffae7db22ee361dccbfb4917
1 parent 224d642 commit 6c6c196

File tree

6 files changed

+215
-33
lines changed

6 files changed

+215
-33
lines changed

src/main/starlark/builtins_bzl/common/proto/proto_common.bzl

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,10 @@ def _Iimport_path_equals_fullpath(proto_source):
3838
return "-I%s=%s" % (_get_import_path(proto_source), proto_source._source_file.path)
3939

4040
def _get_import_path(proto_source):
41-
if proto_source._proto_path == "":
42-
return proto_source._source_file.path
43-
return proto_source._source_file.path[len(proto_source._proto_path) + 1:]
41+
proto_path = proto_source._proto_path
42+
if proto_path and not proto_source._source_file.path.startswith(proto_path + "/"):
43+
fail("Bad proto_path %s for proto %s" % (proto_path, proto_source._source_file.path))
44+
return proto_source._source_file.path.removeprefix(proto_path + "/")
4445

4546
def _compile(
4647
actions,

src/main/starlark/builtins_bzl/common/proto/proto_info.bzl

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,21 @@ ProtoInfo = provider(
3333
"proto_source_root": """(str) The directory relative to which the `.proto` files defined in
3434
the `proto_library` are defined. For example, if this is `a/b` and the rule has the
3535
file `a/b/c/d.proto` as a source, that source file would be imported as
36-
`import c/d.proto`""",
36+
`import c/d.proto`
37+
38+
In principle, the `proto_source_root` directory itself should always
39+
be relative to the output directory (`ctx.bin_dir` or `ctx.genfiles_dir`).
40+
41+
This is at the moment not true for `proto_libraries` using (additional and/or strip)
42+
import prefixes. `proto_source_root` is in this case prefixed with the output
43+
directory. For example, the value is similar to
44+
`bazel-out/k8-fastbuild/bin/a/_virtual_includes/b` for an input file in
45+
`a/_virtual_includes/b/c.proto` that should be imported as `c.proto`.
46+
47+
When using the value please account for both cases in a general way.
48+
That is assume the value is either prefixed with the output directory or not.
49+
This will make it possible to fix `proto_library` in the future.
50+
""",
3751
"transitive_proto_path": """(depset(str) A set of `proto_source_root`s collected from the
3852
transitive closure of this rule.""",
3953
"check_deps_sources": """(depset[File]) The `.proto` sources from the 'srcs' attribute.

src/main/starlark/builtins_bzl/common/proto/proto_library.bzl

Lines changed: 53 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ def _proto_library_impl(ctx):
7171

7272
proto_path, direct_sources = _create_proto_sources(ctx, srcs, import_prefix, strip_import_prefix)
7373
descriptor_set = ctx.actions.declare_file(ctx.label.name + "-descriptor-set.proto.bin")
74-
proto_info = _create_proto_info(direct_sources, deps, proto_path, descriptor_set)
74+
proto_info = _create_proto_info(direct_sources, deps, proto_path, descriptor_set, bin_dir = ctx.bin_dir.path)
7575

7676
_write_descriptor_set(ctx, direct_sources, deps, exports, proto_info, descriptor_set)
7777

@@ -90,6 +90,35 @@ def _proto_library_impl(ctx):
9090
),
9191
]
9292

93+
def _remove_sibling_repo(relpath):
94+
# Addresses sibling repository layout: ../repo/package/path -> package/path
95+
if relpath.startswith("../"):
96+
split = relpath.split("/", 2)
97+
relpath = split[2] if len(split) >= 3 else ""
98+
return relpath
99+
100+
def _from_root(root, relpath):
101+
"""Constructs an exec path from root to relpath"""
102+
if not root:
103+
# `relpath` is a directory with an input source file, the exec path is one of:
104+
# - when in main repo: `package/path`
105+
# - when in a external repository: `external/repo/package/path`
106+
# - with sibling layout: `../repo/package/path`
107+
return relpath
108+
else:
109+
# `relpath` is a directory with a generated file or an output directory:
110+
# - when in main repo: `{root}/package/path`
111+
# - when in an external repository: `{root}/external/repo/package/path`
112+
# - with sibling layout: `{root}/package/path`
113+
return _join(root, _remove_sibling_repo(relpath))
114+
115+
def _empty_to_dot(path):
116+
return path if path else "."
117+
118+
def _uniq(iterable):
119+
unique_elements = {element: None for element in iterable}
120+
return list(unique_elements.keys())
121+
93122
def _create_proto_sources(ctx, srcs, import_prefix, strip_import_prefix):
94123
"""Transforms Files in srcs to ProtoSourceInfos, optionally symlinking them to _virtual_imports.
95124
@@ -105,17 +134,13 @@ def _create_proto_sources(ctx, srcs, import_prefix, strip_import_prefix):
105134
return _symlink_to_virtual_imports(ctx, srcs, import_prefix, strip_import_prefix)
106135
else:
107136
# No virtual source roots
108-
direct_sources = []
109-
for src in srcs:
110-
if ctx.label.workspace_name == "" or ctx.label.workspace_root.startswith(".."):
111-
# source_root == ''|'bazel-out/foo/k8-fastbuild/bin'
112-
source_root = src.root.path
113-
else:
114-
# source_root == ''|'bazel-out/foo/k8-fastbuild/bin' / 'external/repo'
115-
source_root = _join(src.root.path, ctx.label.workspace_root)
116-
direct_sources.append(ProtoSourceInfo(_source_file = src, _proto_path = source_root))
117-
118-
return ctx.label.workspace_root if ctx.label.workspace_root else ".", direct_sources
137+
proto_path = ctx.label.workspace_root # ''|'../repo'|'external/repo'
138+
direct_sources = [ProtoSourceInfo(
139+
_source_file = src,
140+
_proto_path = _from_root(src.root.path, proto_path),
141+
) for src in srcs]
142+
143+
return proto_path, direct_sources
119144

120145
def _join(*path):
121146
return "/".join([p for p in path if p != ""])
@@ -127,12 +152,8 @@ def _symlink_to_virtual_imports(ctx, srcs, import_prefix, strip_import_prefix):
127152
A pair proto_path, directs_sources.
128153
"""
129154
virtual_imports = _join("_virtual_imports", ctx.label.name)
130-
if ctx.label.workspace_name == "" or ctx.label.workspace_root.startswith(".."): # siblingRepositoryLayout
131-
# Example: `bazel-out/[repo/]target/bin / pkg / _virtual_imports/name`
132-
proto_path = _join(ctx.genfiles_dir.path, ctx.label.package, virtual_imports)
133-
else:
134-
# Example: `bazel-out/target/bin / repo / pkg / _virtual_imports/name`
135-
proto_path = _join(ctx.genfiles_dir.path, ctx.label.workspace_root, ctx.label.package, virtual_imports)
155+
proto_path = _join(ctx.label.workspace_root, ctx.label.package, virtual_imports)
156+
root_proto_path = _from_root(ctx.genfiles_dir.path, proto_path)
136157

137158
if ctx.label.workspace_name == "":
138159
full_strip_import_prefix = strip_import_prefix
@@ -156,10 +177,10 @@ def _symlink_to_virtual_imports(ctx, srcs, import_prefix, strip_import_prefix):
156177
target_file = src,
157178
progress_message = "Symlinking virtual .proto sources for %{label}",
158179
)
159-
direct_sources.append(ProtoSourceInfo(_source_file = virtual_src, _proto_path = proto_path))
180+
direct_sources.append(ProtoSourceInfo(_source_file = virtual_src, _proto_path = root_proto_path))
160181
return proto_path, direct_sources
161182

162-
def _create_proto_info(direct_sources, deps, proto_path, descriptor_set):
183+
def _create_proto_info(direct_sources, deps, proto_path, descriptor_set, bin_dir):
163184
"""Constructs ProtoInfo."""
164185

165186
# Construct ProtoInfo
@@ -173,10 +194,15 @@ def _create_proto_info(direct_sources, deps, proto_path, descriptor_set):
173194
transitive = [dep.transitive_sources for dep in deps],
174195
order = "preorder",
175196
)
197+
198+
# There can be up more than 1 direct proto_paths, for example when there's
199+
# a generated and non-generated .proto file in srcs
200+
root_paths = _uniq([src._source_file.root.path for src in direct_sources])
176201
transitive_proto_path = depset(
177-
direct = [proto_path],
202+
direct = [_empty_to_dot(_from_root(root, proto_path)) for root in root_paths],
178203
transitive = [dep.transitive_proto_path for dep in deps],
179204
)
205+
180206
if direct_sources:
181207
check_deps_sources = depset(direct = [src._source_file for src in direct_sources])
182208
else:
@@ -198,7 +224,8 @@ def _create_proto_info(direct_sources, deps, proto_path, descriptor_set):
198224
transitive_sources = transitive_sources,
199225
direct_descriptor_set = descriptor_set,
200226
transitive_descriptor_sets = transitive_descriptor_sets,
201-
proto_source_root = proto_path,
227+
#TODO(b/281812523): remove bin_dir from proto_source_root (when users assuming it's there are migrated)
228+
proto_source_root = _empty_to_dot(_from_root(bin_dir, proto_path) if "_virtual_imports/" in proto_path else _remove_sibling_repo(proto_path)),
202229
transitive_proto_path = transitive_proto_path,
203230
check_deps_sources = check_deps_sources,
204231
transitive_imports = transitive_sources,
@@ -208,7 +235,10 @@ def _create_proto_info(direct_sources, deps, proto_path, descriptor_set):
208235
)
209236

210237
def _get_import_path(proto_source):
211-
return paths.relativize(proto_source._source_file.path, proto_source._proto_path)
238+
proto_path = proto_source._proto_path
239+
if proto_path and not proto_source._source_file.path.startswith(proto_path + "/"):
240+
fail("Bad proto_path %s for proto %s" % (proto_path, proto_source._source_file.path))
241+
return proto_source._source_file.path.removeprefix(proto_path + "/")
212242

213243
def _write_descriptor_set(ctx, direct_sources, deps, exports, proto_info, descriptor_set):
214244
"""Writes descriptor set."""

src/test/java/com/google/devtools/build/lib/rules/proto/BazelProtoCommonTest.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -405,6 +405,7 @@ public void generateCode_directGeneratedProtos() throws Exception {
405405
.comparingElementsUsing(MATCHES_REGEX)
406406
.containsExactly(
407407
"--plugin=bl?azel?-out/[^/]*-exec-[^/]*/bin/third_party/x/plugin",
408+
"--proto_path=bl?azel?-out/k8-fastbuild/bin",
408409
"-Ibar/A.proto=bar/A.proto",
409410
"-Ibar/G.proto=bl?azel?-out/k8-fastbuild/bin/bar/G.proto",
410411
"bar/A.proto",
@@ -436,6 +437,7 @@ public void generateCode_inDirectGeneratedProtos() throws Exception {
436437
.comparingElementsUsing(MATCHES_REGEX)
437438
.containsExactly(
438439
"--plugin=bl?azel?-out/[^/]*-exec-[^/]*/bin/third_party/x/plugin",
440+
"--proto_path=bl?azel?-out/k8-fastbuild/bin",
439441
"-Ibar/A.proto=bar/A.proto",
440442
"-Ibar/G.proto=bl?azel?-out/k8-fastbuild/bin/bar/G.proto",
441443
"bar/A.proto")
@@ -451,22 +453,22 @@ public void generateCode_inDirectGeneratedProtos() throws Exception {
451453
"{virtual: false, sibling: false, generated: false, expectedFlags:"
452454
+ " ['--proto_path=external/foo','-Ie/E.proto=external/foo/e/E.proto']}",
453455
"{virtual: false, sibling: false, generated: true, expectedFlags:"
454-
+ " ['--proto_path=external/foo',"
456+
+ " ['--proto_path=bl?azel?-out/k8-fastbuild/bin/external/foo',"
455457
+ " '-Ie/E.proto=bl?azel?-out/k8-fastbuild/bin/external/foo/e/E.proto']}",
456458
"{virtual: true, sibling: false, generated: false,expectedFlags:"
457459
+ " ['--proto_path=external/foo','-Ie/E.proto=external/foo/e/E.proto']}",
458460
"{virtual: true, sibling: false, generated: true, expectedFlags:"
459461
+ " ['--proto_path=bl?azel?-out/k8-fastbuild/bin/external/foo/e/_virtual_imports/e',"
460462
+ " '-Ie/E.proto=bl?azel?-out/k8-fastbuild/bin/external/foo/e/_virtual_imports/e/e/E.proto']}",
461463
"{virtual: true, sibling: true, generated: false,expectedFlags:"
462-
+ " ['--proto_path=../foo','-I../foo/e/E.proto=../foo/e/E.proto']}",
464+
+ " ['--proto_path=../foo','-Ie/E.proto=../foo/e/E.proto']}",
463465
"{virtual: true, sibling: true, generated: true, expectedFlags:"
464466
+ " ['--proto_path=bl?azel?-out/foo/k8-fastbuild/bin/e/_virtual_imports/e',"
465467
+ " '-Ie/E.proto=bl?azel?-out/foo/k8-fastbuild/bin/e/_virtual_imports/e/e/E.proto']}",
466468
"{virtual: false, sibling: true, generated: false,expectedFlags:"
467-
+ " ['--proto_path=../foo','-I../foo/e/E.proto=../foo/e/E.proto']}",
469+
+ " ['--proto_path=../foo','-Ie/E.proto=../foo/e/E.proto']}",
468470
"{virtual: false, sibling: true, generated: true, expectedFlags:"
469-
+ " ['--proto_path=../foo','-Ie/E.proto=bl?azel?-out/foo/k8-fastbuild/bin/e/E.proto']}",
471+
+ " ['--proto_path=bl?azel?-out/foo/k8-fastbuild/bin','-Ie/E.proto=bl?azel?-out/foo/k8-fastbuild/bin/e/E.proto']}",
470472
})
471473
public void generateCode_externalProtoLibrary(
472474
boolean virtual, boolean sibling, boolean generated, List<String> expectedFlags)

src/test/java/com/google/devtools/build/lib/rules/proto/BazelProtoLibraryTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -449,7 +449,7 @@ private void testExternalRepoWithGeneratedProto(
449449
fooProtoRoot =
450450
genfiles + (siblingRepoLayout ? "" : "/external/foo") + "/x/_virtual_imports/x";
451451
} else {
452-
fooProtoRoot = (siblingRepoLayout ? "../foo" : "external/foo");
452+
fooProtoRoot = (siblingRepoLayout ? genfiles : genfiles + "/external/foo");
453453
}
454454
ConfiguredTarget a = getConfiguredTarget("//a:a");
455455
ProtoInfo aInfo = a.get(ProtoInfo.PROVIDER);

src/test/shell/bazel/bazel_proto_library_test.sh

Lines changed: 136 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ function write_workspace() {
4949
fi
5050

5151
cat $(rlocation io_bazel/src/tests/shell/bazel/rules_proto_stanza.txt) >> "$workspace"WORKSPACE
52+
5253
cat >> "$workspace"WORKSPACE << EOF
5354
load("@rules_proto//proto:repositories.bzl", "rules_proto_dependencies", "rules_proto_toolchains")
5455
rules_proto_dependencies()
@@ -672,6 +673,17 @@ proto_library(
672673
srcs = ["h.proto"],
673674
deps = ["//g", "@repo//f"],
674675
)
676+
677+
cc_proto_library(
678+
name = "h_cc_proto",
679+
deps = ["//h"],
680+
)
681+
682+
java_proto_library(
683+
name = "h_java_proto",
684+
deps = ["//h"],
685+
)
686+
675687
EOF
676688

677689
cat > h/h.proto <<EOF
@@ -687,7 +699,130 @@ message H {
687699
}
688700
EOF
689701

690-
bazel build //h || fail "build failed"
702+
bazel build -s --noexperimental_sibling_repository_layout //h >& $TEST_log || fail "failed"
703+
bazel build -s --noexperimental_sibling_repository_layout //h:h_cc_proto >& $TEST_log || fail "failed"
704+
bazel build -s --noexperimental_sibling_repository_layout //h:h_java_proto >& $TEST_log || fail "failed"
705+
706+
bazel build -s --experimental_sibling_repository_layout //h >& $TEST_log || fail "failed"
707+
bazel build -s --experimental_sibling_repository_layout //h:h_cc_proto >& $TEST_log || fail "failed"
708+
bazel build -s --experimental_sibling_repository_layout //h:h_java_proto >& $TEST_log || fail "failed"
709+
710+
expect_not_log "warning: directory does not exist." # --proto_path is wrong
711+
}
712+
713+
function test_cross_repo_protos() {
714+
mkdir -p e
715+
touch e/WORKSPACE
716+
write_workspace ""
717+
718+
cat >> WORKSPACE <<EOF
719+
local_repository(
720+
name = "repo",
721+
path = "e"
722+
)
723+
EOF
724+
725+
mkdir -p e/f/good
726+
cat > e/f/BUILD <<EOF
727+
load("@rules_proto//proto:defs.bzl", "proto_library")
728+
proto_library(
729+
name = "f",
730+
srcs = ["good/f.proto"],
731+
visibility = ["//visibility:public"],
732+
)
733+
734+
proto_library(
735+
name = "gen",
736+
srcs = ["good/gen.proto"],
737+
visibility = ["//visibility:public"],
738+
)
739+
740+
genrule(name = 'generate', srcs = ['good/gensrc.txt'], cmd = 'cat \$(SRCS) > \$@', outs = ['good/gen.proto'])
741+
742+
EOF
743+
744+
cat > e/f/good/f.proto <<EOF
745+
syntax = "proto2";
746+
package f;
747+
748+
message F {
749+
optional int32 f = 1;
750+
}
751+
EOF
752+
753+
cat > e/f/good/gensrc.txt <<EOF
754+
syntax = "proto2";
755+
package gen;
756+
757+
message Gen {
758+
optional int32 gen = 1;
759+
}
760+
EOF
761+
762+
mkdir -p g/good
763+
cat > g/BUILD << EOF
764+
load("@rules_proto//proto:defs.bzl", "proto_library")
765+
proto_library(
766+
name = 'g',
767+
srcs = ['good/g.proto'],
768+
visibility = ["//visibility:public"],
769+
)
770+
EOF
771+
772+
cat > g/good/g.proto <<EOF
773+
syntax = "proto2";
774+
package g;
775+
776+
message G {
777+
optional int32 g = 1;
778+
}
779+
EOF
780+
781+
mkdir -p h
782+
cat > h/BUILD <<EOF
783+
load("@rules_proto//proto:defs.bzl", "proto_library")
784+
proto_library(
785+
name = "h",
786+
srcs = ["h.proto"],
787+
deps = ["//g", "@repo//f", "@repo//f:gen"],
788+
)
789+
790+
cc_proto_library(
791+
name = "h_cc_proto",
792+
deps = ["//h"],
793+
)
794+
795+
java_proto_library(
796+
name = "h_java_proto",
797+
deps = ["//h"],
798+
)
799+
EOF
800+
801+
cat > h/h.proto <<EOF
802+
syntax = "proto2";
803+
package h;
804+
805+
import "f/good/f.proto";
806+
import "g/good/g.proto";
807+
import "f/good/gen.proto";
808+
809+
message H {
810+
optional f.F f = 1;
811+
optional g.G g = 2;
812+
optional gen.Gen h = 3;
813+
}
814+
EOF
815+
816+
bazel build -s --noexperimental_sibling_repository_layout --noincompatible_generated_protos_in_virtual_imports //h >& $TEST_log || fail "failed"
817+
bazel build -s --noexperimental_sibling_repository_layout --noincompatible_generated_protos_in_virtual_imports //h:h_cc_proto >& $TEST_log || fail "failed"
818+
bazel build -s --noexperimental_sibling_repository_layout --noincompatible_generated_protos_in_virtual_imports //h:h_java_proto >& $TEST_log || fail "failed"
819+
820+
bazel build -s --experimental_sibling_repository_layout --noincompatible_generated_protos_in_virtual_imports //h -s >& $TEST_log || fail "failed"
821+
bazel build -s --experimental_sibling_repository_layout --noincompatible_generated_protos_in_virtual_imports //h:h_cc_proto -s >& $TEST_log || fail "failed"
822+
bazel build -s --experimental_sibling_repository_layout --noincompatible_generated_protos_in_virtual_imports //h:h_java_proto -s >& $TEST_log || fail "failed"
823+
824+
expect_not_log "warning: directory does not exist." # --proto_path is wrong
825+
691826
}
692827

693828
run_suite "Integration tests for proto_library"

0 commit comments

Comments
 (0)