@@ -235,7 +235,7 @@ def name_to_crate_name(name):
235
235
236
236
Note that targets can specify the `crate_name` attribute to customize their
237
237
crate name; in situations where this is important, use the
238
- crate_name_from_attr () function instead.
238
+ compute_crate_name () function instead.
239
239
240
240
Args:
241
241
name (str): The name of the target.
@@ -257,30 +257,39 @@ def _invalid_chars_in_crate_name(name):
257
257
258
258
return dict ([(c , ()) for c in name .elems () if not (c .isalnum () or c == "_" )]).keys ()
259
259
260
- def crate_name_from_attr ( attr ):
260
+ def compute_crate_name ( label , toolchain , name_override = None ):
261
261
"""Returns the crate name to use for the current target.
262
262
263
263
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).
265
267
266
268
Returns:
267
269
str: The crate name to use for this target.
268
270
"""
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 )
271
273
if invalid_chars :
272
274
fail ("Crate name '{}' contains invalid character(s): {}" .format (
273
- attr . crate_name ,
275
+ name_override ,
274
276
" " .join (invalid_chars ),
275
277
))
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 )
277
287
278
- crate_name = name_to_crate_name (attr .name )
279
288
invalid_chars = _invalid_chars_in_crate_name (crate_name )
280
289
if invalid_chars :
281
290
fail (
282
291
"Crate name '{}' " .format (crate_name ) +
283
- "derived from Bazel target name '{}' " .format (attr .name ) +
292
+ "derived from Bazel target name '{}' " .format (label .name ) +
284
293
"contains invalid character(s): {}\n " .format (" " .join (invalid_chars )) +
285
294
"Consider adding a crate_name attribute to set a valid crate name" ,
286
295
)
@@ -382,3 +391,153 @@ def transform_deps(deps):
382
391
build_info = dep [BuildInfo ] if BuildInfo in dep else None ,
383
392
cc_info = dep [CcInfo ] if CcInfo in dep else None ,
384
393
) 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
0 commit comments