Skip to content

Commit 6630fd5

Browse files
cfredrichlopko
andauthored
Add rename_first_party_crates and third_party_dir flags (#1056)
Co-authored-by: cfredric <[email protected]> Co-authored-by: Marcel Hlopko <[email protected]> Co-authored-by: Marcel Hlopko <[email protected]>
1 parent 38211c2 commit 6630fd5

File tree

13 files changed

+512
-13
lines changed

13 files changed

+512
-13
lines changed

docs/BUILD.bazel

+1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ bzl_library(
1313
],
1414
deps = [
1515
"@bazel_skylib//lib:paths",
16+
"@bazel_skylib//rules:common_settings",
1617
"@rules_proto//proto:defs",
1718
"@rules_proto//proto:repositories",
1819
],

rust/private/rust.bzl

+4-4
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ load("//rust/private:common.bzl", "rust_common")
1717
load("//rust/private:rustc.bzl", "rustc_compile_action")
1818
load(
1919
"//rust/private:utils.bzl",
20-
"crate_name_from_attr",
20+
"compute_crate_name",
2121
"dedent",
2222
"determine_output_hash",
2323
"expand_dict_value_locations",
@@ -253,7 +253,7 @@ def _rust_library_common(ctx, crate_type):
253253
else:
254254
output_hash = None
255255

256-
crate_name = crate_name_from_attr(ctx.attr)
256+
crate_name = compute_crate_name(ctx.label, toolchain, ctx.attr.crate_name)
257257
rust_lib_name = _determine_lib_name(
258258
crate_name,
259259
crate_type,
@@ -297,7 +297,7 @@ def _rust_binary_impl(ctx):
297297
list: A list of providers. See `rustc_compile_action`
298298
"""
299299
toolchain = find_toolchain(ctx)
300-
crate_name = crate_name_from_attr(ctx.attr)
300+
crate_name = compute_crate_name(ctx.label, toolchain, ctx.attr.crate_name)
301301
_assert_correct_dep_mapping(ctx)
302302

303303
output = ctx.actions.declare_file(ctx.label.name + toolchain.binary_ext)
@@ -432,7 +432,7 @@ def _rust_test_common(ctx, toolchain, output):
432432
_assert_no_deprecated_attributes(ctx)
433433
_assert_correct_dep_mapping(ctx)
434434

435-
crate_name = crate_name_from_attr(ctx.attr)
435+
crate_name = compute_crate_name(ctx.label, toolchain, ctx.attr.crate_name)
436436
crate_type = "bin"
437437

438438
deps = transform_deps(ctx.attr.deps)

rust/private/utils.bzl

+168-9
Original file line numberDiff line numberDiff line change
@@ -235,7 +235,7 @@ def name_to_crate_name(name):
235235
236236
Note that targets can specify the `crate_name` attribute to customize their
237237
crate name; in situations where this is important, use the
238-
crate_name_from_attr() function instead.
238+
compute_crate_name() function instead.
239239
240240
Args:
241241
name (str): The name of the target.
@@ -257,30 +257,39 @@ def _invalid_chars_in_crate_name(name):
257257

258258
return dict([(c, ()) for c in name.elems() if not (c.isalnum() or c == "_")]).keys()
259259

260-
def crate_name_from_attr(attr):
260+
def compute_crate_name(label, toolchain, name_override = None):
261261
"""Returns the crate name to use for the current target.
262262
263263
Args:
264-
attr (struct): The attributes of the current target.
264+
label (struct): The label of the current target.
265+
toolchain (struct): The toolchain in use for the target.
266+
name_override (String): An optional name to use (as an override of label.name).
265267
266268
Returns:
267269
str: The crate name to use for this target.
268270
"""
269-
if hasattr(attr, "crate_name") and attr.crate_name:
270-
invalid_chars = _invalid_chars_in_crate_name(attr.crate_name)
271+
if name_override:
272+
invalid_chars = _invalid_chars_in_crate_name(name_override)
271273
if invalid_chars:
272274
fail("Crate name '{}' contains invalid character(s): {}".format(
273-
attr.crate_name,
275+
name_override,
274276
" ".join(invalid_chars),
275277
))
276-
return attr.crate_name
278+
return name_override
279+
280+
if toolchain and label and toolchain._rename_first_party_crates:
281+
if should_encode_label_in_crate_name(label.package, toolchain._third_party_dir):
282+
crate_name = label.name
283+
else:
284+
crate_name = encode_label_as_crate_name(label.package, label.name)
285+
else:
286+
crate_name = name_to_crate_name(label.name)
277287

278-
crate_name = name_to_crate_name(attr.name)
279288
invalid_chars = _invalid_chars_in_crate_name(crate_name)
280289
if invalid_chars:
281290
fail(
282291
"Crate name '{}' ".format(crate_name) +
283-
"derived from Bazel target name '{}' ".format(attr.name) +
292+
"derived from Bazel target name '{}' ".format(label.name) +
284293
"contains invalid character(s): {}\n".format(" ".join(invalid_chars)) +
285294
"Consider adding a crate_name attribute to set a valid crate name",
286295
)
@@ -382,3 +391,153 @@ def transform_deps(deps):
382391
build_info = dep[BuildInfo] if BuildInfo in dep else None,
383392
cc_info = dep[CcInfo] if CcInfo in dep else None,
384393
) for dep in deps]
394+
395+
def should_encode_label_in_crate_name(package, third_party_dir):
396+
"""Determines if the crate's name should include the Bazel label, encoded.
397+
398+
Names of third-party crates do not encode the full label.
399+
400+
Args:
401+
package (string): The package in question.
402+
third_party_dir (string): The directory in which third-party packages are kept.
403+
404+
Returns:
405+
True if the crate name should encode the label, False otherwise.
406+
"""
407+
408+
# TODO(hlopko): This code assumes a monorepo; make it work with external
409+
# repositories as well.
410+
return ("//" + package + "/").startswith(third_party_dir + "/")
411+
412+
# This is a list of pairs, where the first element of the pair is a character
413+
# that is allowed in Bazel package or target names but not in crate names; and
414+
# the second element is an encoding of that char suitable for use in a crate
415+
# name.
416+
_encodings = (
417+
(":", "colon"),
418+
("!", "bang"),
419+
("%", "percent"),
420+
("@", "at"),
421+
("^", "caret"),
422+
("`", "backtick"),
423+
(" ", "space"),
424+
("\"", "quote"),
425+
("#", "hash"),
426+
("$", "dollar"),
427+
("&", "ampersand"),
428+
("'", "backslash"),
429+
("(", "lparen"),
430+
(")", "rparen"),
431+
("*", "star"),
432+
("-", "dash"),
433+
("+", "plus"),
434+
(",", "comma"),
435+
(";", "semicolon"),
436+
("<", "langle"),
437+
("=", "equal"),
438+
(">", "rangle"),
439+
("?", "question"),
440+
("[", "lbracket"),
441+
("]", "rbracket"),
442+
("{", "lbrace"),
443+
("|", "pipe"),
444+
("}", "rbrace"),
445+
("~", "tilde"),
446+
("/", "slash"),
447+
(".", "dot"),
448+
)
449+
450+
# For each of the above encodings, we generate two substitution rules: one that
451+
# ensures any occurrences of the encodings themselves in the package/target
452+
# aren't clobbered by this translation, and one that does the encoding itself.
453+
# We also include a rule that protects the clobbering-protection rules from
454+
# getting clobbered.
455+
_substitutions = [("_quote", "_quotequote_")] + [
456+
subst
457+
for (pattern, replacement) in _encodings
458+
for subst in (
459+
("_{}_".format(replacement), "_quote{}_".format(replacement)),
460+
(pattern, "_{}_".format(replacement)),
461+
)
462+
]
463+
464+
def encode_label_as_crate_name(package, name):
465+
"""Encodes the package and target names in a format suitable for a crate name.
466+
467+
Args:
468+
package (string): The package of the target in question.
469+
name (string): The name of the target in question.
470+
471+
Returns:
472+
A string that encodes the package and target name, to be used as the crate's name.
473+
"""
474+
full_name = package + ":" + name
475+
return _replace_all(full_name, _substitutions)
476+
477+
def decode_crate_name_as_label_for_testing(crate_name):
478+
"""Decodes a crate_name that was encoded by encode_label_as_crate_name.
479+
480+
This is used to check that the encoding is bijective; it is expected to only
481+
be used in tests.
482+
483+
Args:
484+
crate_name (string): The name of the crate.
485+
486+
Returns:
487+
A string representing the Bazel label (package and target).
488+
"""
489+
return _replace_all(crate_name, [(t[1], t[0]) for t in _substitutions])
490+
491+
def _replace_all(string, substitutions):
492+
"""Replaces occurrences of the given patterns in `string`.
493+
494+
Args:
495+
string (string): the string in which the replacements should be performed.
496+
substitutions: the list of patterns and replacements to apply.
497+
498+
Returns:
499+
A string with the appropriate substitutions performed.
500+
501+
There are a few reasons this looks complicated:
502+
* The substitutions are performed with some priority, i.e. patterns that are
503+
listed first in `substitutions` are higher priority than patterns that are
504+
listed later.
505+
* We also take pains to avoid doing replacements that overlap with each
506+
other, since overlaps invalidate pattern matches.
507+
* To avoid hairy offset invalidation, we apply the substitutions
508+
right-to-left.
509+
* To avoid the "_quote" -> "_quotequote_" rule introducing new pattern
510+
matches later in the string during decoding, we take the leftmost
511+
replacement, in cases of overlap. (Note that no rule can induce new
512+
pattern matches *earlier* in the string.) (E.g. "_quotedot_" encodes to
513+
"_quotequote_dot_". Note that "_quotequote_" and "_dot_" both occur in
514+
this string, and overlap.).
515+
"""
516+
517+
# Find the highest-priority pattern matches.
518+
plan = {}
519+
for subst_index, (pattern, replacement) in enumerate(substitutions):
520+
for pattern_start in range(len(string)):
521+
if not pattern_start in plan and string.startswith(pattern, pattern_start):
522+
plan[pattern_start] = (len(pattern), replacement)
523+
524+
# Drop replacements that overlap with a replacement earlier in the string.
525+
replaced_indices_set = {}
526+
leftmost_plan = {}
527+
for pattern_start in sorted(plan.keys()):
528+
length, _ = plan[pattern_start]
529+
pattern_indices = list(range(pattern_start, pattern_start + length))
530+
if any([i in replaced_indices_set for i in pattern_indices]):
531+
continue
532+
replaced_indices_set.update([(i, True) for i in pattern_indices])
533+
leftmost_plan[pattern_start] = plan[pattern_start]
534+
535+
plan = leftmost_plan
536+
537+
# Execute the replacement plan, working from right to left.
538+
for pattern_start in sorted(plan.keys(), reverse = True):
539+
length, replacement = plan[pattern_start]
540+
after_pattern = pattern_start + length
541+
string = string[:pattern_start] + replacement + string[after_pattern:]
542+
543+
return string

rust/settings/BUILD.bazel

+20
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
load("@bazel_skylib//:bzl_library.bzl", "bzl_library")
2+
load("@bazel_skylib//rules:common_settings.bzl", "bool_flag", "string_flag")
23
load(":incompatible.bzl", "incompatible_flag")
34

45
package(default_visibility = ["//visibility:public"])
@@ -15,6 +16,25 @@ incompatible_flag(
1516
issue = "https://github.com/bazelbuild/rules_rust/issues/1069",
1617
)
1718

19+
# A flag controlling whether to rename first-party crates such that their names
20+
# encode the Bazel package and target name, instead of just the target name.
21+
#
22+
# First-party vs. third-party crates are identified using the value of
23+
# //settings:third_party_dir.
24+
bool_flag(
25+
name = "rename_first_party_crates",
26+
build_setting_default = False,
27+
)
28+
29+
# A flag specifying the location of vendored third-party rust crates within this
30+
# repository that must not be renamed when `rename_1p_crates` is enabled.
31+
#
32+
# Must be specified as a Bazel package, e.g. "//some/location/in/repo".
33+
string_flag(
34+
name = "third_party_dir",
35+
build_setting_default = "//third_party/rust",
36+
)
37+
1838
bzl_library(
1939
name = "rules",
2040
srcs = glob(["**/*.bzl"]),

rust/toolchain.bzl

+12
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"""The rust_toolchain rule definition and implementation."""
22

3+
load("@bazel_skylib//rules:common_settings.bzl", "BuildSettingInfo")
34
load("//rust/private:common.bzl", "rust_common")
45
load("//rust/private:utils.bzl", "dedent", "find_cc_toolchain", "make_static_lib_symlink")
56
load("//rust/settings:incompatible.bzl", "IncompatibleFlagInfo")
@@ -234,6 +235,9 @@ def _rust_toolchain_impl(ctx):
234235
remove_transitive_libs_from_dep_info = ctx.attr._incompatible_remove_transitive_libs_from_dep_info[IncompatibleFlagInfo]
235236
disable_custom_test_launcher = ctx.attr._incompatible_disable_custom_test_launcher[IncompatibleFlagInfo]
236237

238+
rename_first_party_crates = ctx.attr._rename_first_party_crates[BuildSettingInfo].value
239+
third_party_dir = ctx.attr._third_party_dir[BuildSettingInfo].value
240+
237241
expanded_stdlib_linkflags = []
238242
for flag in ctx.attr.stdlib_linkflags:
239243
expanded_stdlib_linkflags.append(
@@ -286,6 +290,8 @@ def _rust_toolchain_impl(ctx):
286290
libstd_and_allocator_ccinfo = _make_libstd_and_allocator_ccinfo(ctx, ctx.attr.rust_lib, ctx.attr.allocator_library),
287291
_incompatible_remove_transitive_libs_from_dep_info = remove_transitive_libs_from_dep_info.enabled,
288292
_incompatible_disable_custom_test_launcher = disable_custom_test_launcher.enabled,
293+
_rename_first_party_crates = rename_first_party_crates,
294+
_third_party_dir = third_party_dir,
289295
)
290296
return [toolchain]
291297

@@ -404,6 +410,12 @@ rust_toolchain = rule(
404410
"_incompatible_remove_transitive_libs_from_dep_info": attr.label(
405411
default = "@rules_rust//rust/settings:incompatible_remove_transitive_libs_from_dep_info",
406412
),
413+
"_rename_first_party_crates": attr.label(
414+
default = "@rules_rust//rust/settings:rename_first_party_crates",
415+
),
416+
"_third_party_dir": attr.label(
417+
default = "@rules_rust//rust/settings:third_party_dir",
418+
),
407419
},
408420
toolchains = [
409421
"@bazel_tools//tools/cpp:toolchain_type",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
load(":rename_first_party_crates_test.bzl", "rename_first_party_crates_test_suite")
2+
3+
############################ UNIT TESTS #############################
4+
rename_first_party_crates_test_suite(name = "rename_first_party_crates_test_suite")
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
load("//rust:defs.bzl", "rust_library")
2+
3+
rust_library(
4+
name = "third_party_lib",
5+
srcs = ["lib.rs"],
6+
visibility = ["//visibility:public"],
7+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+

0 commit comments

Comments
 (0)