blob: 879e2b62c552627f280520b4cf8bd1f194770968 [file] [log] [blame]
Brian Silvermancc09f182022-03-09 15:40:20 -08001# Copyright 2015 The Bazel Authors. All rights reserved.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15"""Utility functions not specific to the rust toolchain."""
16
17load("@bazel_tools//tools/cpp:toolchain_utils.bzl", find_rules_cc_toolchain = "find_cpp_toolchain")
Adam Snaider1c095c92023-07-08 02:09:58 -040018load(":providers.bzl", "BuildInfo", "CrateGroupInfo", "CrateInfo", "DepInfo", "DepVariantInfo")
Brian Silvermancc09f182022-03-09 15:40:20 -080019
Brian Silverman5f6f2762022-08-13 19:30:05 -070020UNSUPPORTED_FEATURES = [
21 "thin_lto",
22 "module_maps",
23 "use_header_modules",
24 "fdo_instrument",
25 "fdo_optimize",
26]
27
Brian Silvermancc09f182022-03-09 15:40:20 -080028def find_toolchain(ctx):
29 """Finds the first rust toolchain that is configured.
30
31 Args:
32 ctx (ctx): The ctx object for the current target.
33
34 Returns:
35 rust_toolchain: A Rust toolchain context.
36 """
Brian Silverman5f6f2762022-08-13 19:30:05 -070037 return ctx.toolchains[Label("//rust:toolchain_type")]
Brian Silvermancc09f182022-03-09 15:40:20 -080038
39def find_cc_toolchain(ctx):
40 """Extracts a CcToolchain from the current target's context
41
42 Args:
43 ctx (ctx): The current target's rule context object
44
45 Returns:
46 tuple: A tuple of (CcToolchain, FeatureConfiguration)
47 """
48 cc_toolchain = find_rules_cc_toolchain(ctx)
49
50 feature_configuration = cc_common.configure_features(
51 ctx = ctx,
52 cc_toolchain = cc_toolchain,
53 requested_features = ctx.features,
Brian Silverman5f6f2762022-08-13 19:30:05 -070054 unsupported_features = UNSUPPORTED_FEATURES + ctx.disabled_features,
Brian Silvermancc09f182022-03-09 15:40:20 -080055 )
56 return cc_toolchain, feature_configuration
57
58# TODO: Replace with bazel-skylib's `path.dirname`. This requires addressing some
59# dependency issues or generating docs will break.
60def relativize(path, start):
61 """Returns the relative path from start to path.
62
63 Args:
64 path (str): The path to relativize.
65 start (str): The ancestor path against which to relativize.
66
67 Returns:
68 str: The portion of `path` that is relative to `start`.
69 """
70 src_parts = _path_parts(start)
71 dest_parts = _path_parts(path)
72 n = 0
73 for src_part, dest_part in zip(src_parts, dest_parts):
74 if src_part != dest_part:
75 break
76 n += 1
77
78 relative_path = ""
79 for _ in range(n, len(src_parts)):
80 relative_path += "../"
81 relative_path += "/".join(dest_parts[n:])
82
83 return relative_path
84
85def _path_parts(path):
86 """Takes a path and returns a list of its parts with all "." elements removed.
87
88 The main use case of this function is if one of the inputs to relativize()
89 is a relative path, such as "./foo".
90
91 Args:
92 path (str): A string representing a unix path
93
94 Returns:
95 list: A list containing the path parts with all "." elements removed.
96 """
97 path_parts = path.split("/")
98 return [part for part in path_parts if part != "."]
99
Brian Silverman5f6f2762022-08-13 19:30:05 -0700100def get_lib_name_default(lib):
101 """Returns the name of a library artifact.
Brian Silvermancc09f182022-03-09 15:40:20 -0800102
103 Args:
104 lib (File): A library file
105
106 Returns:
107 str: The name of the library
108 """
109 # On macos and windows, dynamic/static libraries always end with the
110 # extension and potential versions will be before the extension, and should
111 # be part of the library name.
112 # On linux, the version usually comes after the extension.
113 # So regardless of the platform we want to find the extension and make
114 # everything left to it the library name.
115
116 # Search for the extension - starting from the right - by removing any
117 # trailing digit.
118 comps = lib.basename.split(".")
119 for comp in reversed(comps):
120 if comp.isdigit():
121 comps.pop()
122 else:
123 break
124
125 # The library name is now everything minus the extension.
126 libname = ".".join(comps[:-1])
127
128 if libname.startswith("lib"):
129 return libname[3:]
130 else:
131 return libname
132
Brian Silverman5f6f2762022-08-13 19:30:05 -0700133# TODO: Could we remove this function in favor of a "windows" parameter in the
134# above function? It looks like currently lambdas cannot accept local parameters
135# so the following doesn't work:
136# args.add_all(
137# cc_toolchain.dynamic_runtime_lib(feature_configuration = feature_configuration),
Adam Snaider1c095c92023-07-08 02:09:58 -0400138# map_each = lambda x: get_lib_name(x, for_windows = toolchain.target_os.startswith("windows)),
Brian Silverman5f6f2762022-08-13 19:30:05 -0700139# format_each = "-ldylib=%s",
140# )
141def get_lib_name_for_windows(lib):
142 """Returns the name of a library artifact for Windows builds.
143
144 Args:
145 lib (File): A library file
146
147 Returns:
148 str: The name of the library
149 """
150 # On macos and windows, dynamic/static libraries always end with the
151 # extension and potential versions will be before the extension, and should
152 # be part of the library name.
153 # On linux, the version usually comes after the extension.
154 # So regardless of the platform we want to find the extension and make
155 # everything left to it the library name.
156
157 # Search for the extension - starting from the right - by removing any
158 # trailing digit.
159 comps = lib.basename.split(".")
160 for comp in reversed(comps):
161 if comp.isdigit():
162 comps.pop()
163 else:
164 break
165
166 # The library name is now everything minus the extension.
167 libname = ".".join(comps[:-1])
168
169 return libname
170
Brian Silvermancc09f182022-03-09 15:40:20 -0800171def abs(value):
172 """Returns the absolute value of a number.
173
174 Args:
175 value (int): A number.
176
177 Returns:
178 int: The absolute value of the number.
179 """
180 if value < 0:
181 return -value
182 return value
183
184def determine_output_hash(crate_root, label):
185 """Generates a hash of the crate root file's path.
186
187 Args:
188 crate_root (File): The crate's root file (typically `lib.rs`).
189 label (Label): The label of the target.
190
191 Returns:
192 str: A string representation of the hash.
193 """
194
195 # Take the absolute value of hash() since it could be negative.
196 h = abs(hash(crate_root.path) + hash(repr(label)))
197 return repr(h)
198
199def get_preferred_artifact(library_to_link, use_pic):
200 """Get the first available library to link from a LibraryToLink object.
201
202 Args:
203 library_to_link (LibraryToLink): See the followg links for additional details:
204 https://docs.bazel.build/versions/master/skylark/lib/LibraryToLink.html
205 use_pic: If set, prefers pic_static_library over static_library.
206
207 Returns:
208 File: Returns the first valid library type (only one is expected)
209 """
210 if use_pic:
Brian Silverman5f6f2762022-08-13 19:30:05 -0700211 # Order consistent with https://github.com/bazelbuild/bazel/blob/815dfdabb7df31d4e99b6fc7616ff8e2f9385d98/src/main/java/com/google/devtools/build/lib/rules/cpp/CcLinkingContext.java#L437.
Brian Silvermancc09f182022-03-09 15:40:20 -0800212 return (
213 library_to_link.pic_static_library or
Brian Silverman5f6f2762022-08-13 19:30:05 -0700214 library_to_link.static_library or
Brian Silvermancc09f182022-03-09 15:40:20 -0800215 library_to_link.interface_library or
216 library_to_link.dynamic_library
217 )
218 else:
219 return (
220 library_to_link.static_library or
221 library_to_link.pic_static_library or
222 library_to_link.interface_library or
223 library_to_link.dynamic_library
224 )
225
Adam Snaider1c095c92023-07-08 02:09:58 -0400226# The normal ctx.expand_location, but with an additional deduplication step.
227# We do this to work around a potential crash, see
228# https://github.com/bazelbuild/bazel/issues/16664
229def dedup_expand_location(ctx, input, targets = []):
230 return ctx.expand_location(input, _deduplicate(targets))
231
232def _deduplicate(xs):
233 return {x: True for x in xs}.keys()
234
235def concat(xss):
236 return [x for xs in xss for x in xs]
237
238def _expand_location_for_build_script_runner(ctx, env, data):
Brian Silverman5f6f2762022-08-13 19:30:05 -0700239 """A trivial helper for `expand_dict_value_locations` and `expand_list_element_locations`
Brian Silvermancc09f182022-03-09 15:40:20 -0800240
241 Args:
242 ctx (ctx): The rule's context object
243 env (str): The value possibly containing location macros to expand.
Brian Silverman5f6f2762022-08-13 19:30:05 -0700244 data (sequence of Targets): See one of the parent functions.
Brian Silvermancc09f182022-03-09 15:40:20 -0800245
246 Returns:
247 string: The location-macro expanded version of the string.
248 """
249 for directive in ("$(execpath ", "$(location "):
250 if directive in env:
251 # build script runner will expand pwd to execroot for us
Brian Silverman5f6f2762022-08-13 19:30:05 -0700252 env = env.replace(directive, "$${pwd}/" + directive)
253 return ctx.expand_make_variables(
254 env,
Adam Snaider1c095c92023-07-08 02:09:58 -0400255 dedup_expand_location(ctx, env, data),
Brian Silverman5f6f2762022-08-13 19:30:05 -0700256 {},
257 )
Brian Silvermancc09f182022-03-09 15:40:20 -0800258
259def expand_dict_value_locations(ctx, env, data):
260 """Performs location-macro expansion on string values.
261
262 $(execroot ...) and $(location ...) are prefixed with ${pwd},
263 which process_wrapper and build_script_runner will expand at run time
264 to the absolute path. This is necessary because include_str!() is relative
265 to the currently compiled file, and build scripts run relative to the
266 manifest dir, so we can not use execroot-relative paths.
267
268 $(rootpath ...) is unmodified, and is useful for passing in paths via
269 rustc_env that are encoded in the binary with env!(), but utilized at
270 runtime, such as in tests. The absolute paths are not usable in this case,
271 as compilation happens in a separate sandbox folder, so when it comes time
272 to read the file at runtime, the path is no longer valid.
273
Brian Silverman5f6f2762022-08-13 19:30:05 -0700274 For detailed documentation, see:
275 - [`expand_location`](https://bazel.build/rules/lib/ctx#expand_location)
276 - [`expand_make_variables`](https://bazel.build/rules/lib/ctx#expand_make_variables)
Brian Silvermancc09f182022-03-09 15:40:20 -0800277
278 Args:
279 ctx (ctx): The rule's context object
280 env (dict): A dict whose values we iterate over
281 data (sequence of Targets): The targets which may be referenced by
282 location macros. This is expected to be the `data` attribute of
283 the target, though may have other targets or attributes mixed in.
284
285 Returns:
286 dict: A dict of environment variables with expanded location macros
287 """
Adam Snaider1c095c92023-07-08 02:09:58 -0400288 return dict([(k, _expand_location_for_build_script_runner(ctx, v, data)) for (k, v) in env.items()])
Brian Silvermancc09f182022-03-09 15:40:20 -0800289
290def expand_list_element_locations(ctx, args, data):
291 """Performs location-macro expansion on a list of string values.
292
293 $(execroot ...) and $(location ...) are prefixed with ${pwd},
294 which process_wrapper and build_script_runner will expand at run time
295 to the absolute path.
296
Brian Silverman5f6f2762022-08-13 19:30:05 -0700297 For detailed documentation, see:
298 - [`expand_location`](https://bazel.build/rules/lib/ctx#expand_location)
299 - [`expand_make_variables`](https://bazel.build/rules/lib/ctx#expand_make_variables)
Brian Silvermancc09f182022-03-09 15:40:20 -0800300
301 Args:
302 ctx (ctx): The rule's context object
303 args (list): A list we iterate over
304 data (sequence of Targets): The targets which may be referenced by
305 location macros. This is expected to be the `data` attribute of
306 the target, though may have other targets or attributes mixed in.
307
308 Returns:
309 list: A list of arguments with expanded location macros
310 """
Adam Snaider1c095c92023-07-08 02:09:58 -0400311 return [_expand_location_for_build_script_runner(ctx, arg, data) for arg in args]
Brian Silvermancc09f182022-03-09 15:40:20 -0800312
313def name_to_crate_name(name):
314 """Converts a build target's name into the name of its associated crate.
315
316 Crate names cannot contain certain characters, such as -, which are allowed
317 in build target names. All illegal characters will be converted to
318 underscores.
319
320 This is a similar conversion as that which cargo does, taking a
321 `Cargo.toml`'s `package.name` and canonicalizing it
322
323 Note that targets can specify the `crate_name` attribute to customize their
324 crate name; in situations where this is important, use the
325 compute_crate_name() function instead.
326
327 Args:
328 name (str): The name of the target.
329
330 Returns:
331 str: The name of the crate for this target.
332 """
Brian Silverman5f6f2762022-08-13 19:30:05 -0700333 for illegal in ("-", "/"):
334 name = name.replace(illegal, "_")
335 return name
Brian Silvermancc09f182022-03-09 15:40:20 -0800336
337def _invalid_chars_in_crate_name(name):
338 """Returns any invalid chars in the given crate name.
339
340 Args:
341 name (str): Name to test.
342
343 Returns:
344 list: List of invalid characters in the crate name.
345 """
346
347 return dict([(c, ()) for c in name.elems() if not (c.isalnum() or c == "_")]).keys()
348
349def compute_crate_name(workspace_name, label, toolchain, name_override = None):
350 """Returns the crate name to use for the current target.
351
352 Args:
353 workspace_name (string): The current workspace name.
354 label (struct): The label of the current target.
355 toolchain (struct): The toolchain in use for the target.
356 name_override (String): An optional name to use (as an override of label.name).
357
358 Returns:
359 str: The crate name to use for this target.
360 """
361 if name_override:
362 invalid_chars = _invalid_chars_in_crate_name(name_override)
363 if invalid_chars:
364 fail("Crate name '{}' contains invalid character(s): {}".format(
365 name_override,
366 " ".join(invalid_chars),
367 ))
368 return name_override
369
370 if (toolchain and label and toolchain._rename_first_party_crates and
371 should_encode_label_in_crate_name(workspace_name, label, toolchain._third_party_dir)):
372 crate_name = encode_label_as_crate_name(label.package, label.name)
373 else:
374 crate_name = name_to_crate_name(label.name)
375
376 invalid_chars = _invalid_chars_in_crate_name(crate_name)
377 if invalid_chars:
378 fail(
379 "Crate name '{}' ".format(crate_name) +
380 "derived from Bazel target name '{}' ".format(label.name) +
381 "contains invalid character(s): {}\n".format(" ".join(invalid_chars)) +
382 "Consider adding a crate_name attribute to set a valid crate name",
383 )
384 return crate_name
385
386def dedent(doc_string):
387 """Remove any common leading whitespace from every line in text.
388
389 This functionality is similar to python's `textwrap.dedent` functionality
390 https://docs.python.org/3/library/textwrap.html#textwrap.dedent
391
392 Args:
393 doc_string (str): A docstring style string
394
395 Returns:
396 str: A string optimized for stardoc rendering
397 """
398 lines = doc_string.splitlines()
399 if not lines:
400 return doc_string
401
402 # If the first line is empty, use the second line
403 first_line = lines[0]
404 if not first_line:
405 first_line = lines[1]
406
407 # Detect how much space prepends the first line and subtract that from all lines
408 space_count = len(first_line) - len(first_line.lstrip())
409
410 # If there are no leading spaces, do not alter the docstring
411 if space_count == 0:
412 return doc_string
413 else:
414 # Remove the leading block of spaces from the current line
415 block = " " * space_count
416 return "\n".join([line.replace(block, "", 1).rstrip() for line in lines])
417
418def make_static_lib_symlink(actions, rlib_file):
419 """Add a .a symlink to an .rlib file.
420
421 The name of the symlink is derived from the <name> of the <name>.rlib file as follows:
422 * `<name>.a`, if <name> starts with `lib`
423 * `lib<name>.a`, otherwise.
424
425 For example, the name of the symlink for
426 * `libcratea.rlib` is `libcratea.a`
427 * `crateb.rlib` is `libcrateb.a`.
428
429 Args:
430 actions (actions): The rule's context actions object.
431 rlib_file (File): The file to symlink, which must end in .rlib.
432
433 Returns:
434 The symlink's File.
435 """
436 if not rlib_file.basename.endswith(".rlib"):
437 fail("file is not an .rlib: ", rlib_file.basename)
438 basename = rlib_file.basename[:-5]
439 if not basename.startswith("lib"):
440 basename = "lib" + basename
441 dot_a = actions.declare_file(basename + ".a", sibling = rlib_file)
442 actions.symlink(output = dot_a, target_file = rlib_file)
443 return dot_a
444
445def is_exec_configuration(ctx):
446 """Determine if a context is building for the exec configuration.
447
448 This is helpful when processing command line flags that should apply
449 to the target configuration but not the exec configuration.
450
451 Args:
452 ctx (ctx): The ctx object for the current target.
453
454 Returns:
455 True if the exec configuration is detected, False otherwise.
456 """
457
458 # TODO(djmarcin): Is there any better way to determine cfg=exec?
459 return ctx.genfiles_dir.path.find("-exec-") != -1
460
461def transform_deps(deps):
462 """Transforms a [Target] into [DepVariantInfo].
463
464 This helper function is used to transform ctx.attr.deps and ctx.attr.proc_macro_deps into
465 [DepVariantInfo].
466
467 Args:
468 deps (list of Targets): Dependencies coming from ctx.attr.deps or ctx.attr.proc_macro_deps
469
470 Returns:
471 list of DepVariantInfos.
472 """
473 return [DepVariantInfo(
474 crate_info = dep[CrateInfo] if CrateInfo in dep else None,
475 dep_info = dep[DepInfo] if DepInfo in dep else None,
476 build_info = dep[BuildInfo] if BuildInfo in dep else None,
477 cc_info = dep[CcInfo] if CcInfo in dep else None,
Adam Snaider1c095c92023-07-08 02:09:58 -0400478 crate_group_info = dep[CrateGroupInfo] if CrateGroupInfo in dep else None,
Brian Silvermancc09f182022-03-09 15:40:20 -0800479 ) for dep in deps]
480
481def get_import_macro_deps(ctx):
482 """Returns a list of targets to be added to proc_macro_deps.
483
484 Args:
485 ctx (struct): the ctx of the current target.
486
487 Returns:
488 list of Targets. Either empty (if the fake import macro implementation
489 is being used), or a singleton list with the real implementation.
490 """
491 if ctx.attr._import_macro_dep.label.name == "fake_import_macro_impl":
492 return []
493
494 return [ctx.attr._import_macro_dep]
495
496def should_encode_label_in_crate_name(workspace_name, label, third_party_dir):
497 """Determines if the crate's name should include the Bazel label, encoded.
498
499 Crate names may only encode the label if the target is in the current repo,
500 the target is not in the third_party_dir, and the current repo is not
501 rules_rust.
502
503 Args:
504 workspace_name (string): The name of the current workspace.
505 label (Label): The package in question.
506 third_party_dir (string): The directory in which third-party packages are kept.
507
508 Returns:
509 True if the crate name should encode the label, False otherwise.
510 """
511
512 # TODO(hlopko): This code assumes a monorepo; make it work with external
513 # repositories as well.
514 return (
515 workspace_name != "rules_rust" and
516 not label.workspace_root and
517 not ("//" + label.package + "/").startswith(third_party_dir + "/")
518 )
519
520# This is a list of pairs, where the first element of the pair is a character
521# that is allowed in Bazel package or target names but not in crate names; and
522# the second element is an encoding of that char suitable for use in a crate
523# name.
524_encodings = (
Brian Silverman5f6f2762022-08-13 19:30:05 -0700525 (":", "x"),
526 ("!", "excl"),
527 ("%", "prc"),
528 ("@", "ao"),
Brian Silvermancc09f182022-03-09 15:40:20 -0800529 ("^", "caret"),
Brian Silverman5f6f2762022-08-13 19:30:05 -0700530 ("`", "bt"),
531 (" ", "sp"),
532 ("\"", "dq"),
533 ("#", "octo"),
534 ("$", "dllr"),
535 ("&", "amp"),
536 ("'", "sq"),
537 ("(", "lp"),
538 (")", "rp"),
539 ("*", "astr"),
540 ("-", "d"),
541 ("+", "pl"),
542 (",", "cm"),
543 (";", "sm"),
544 ("<", "la"),
545 ("=", "eq"),
546 (">", "ra"),
547 ("?", "qm"),
548 ("[", "lbk"),
549 ("]", "rbk"),
550 ("{", "lbe"),
551 ("|", "pp"),
552 ("}", "rbe"),
553 ("~", "td"),
554 ("/", "y"),
555 (".", "pd"),
Brian Silvermancc09f182022-03-09 15:40:20 -0800556)
557
558# For each of the above encodings, we generate two substitution rules: one that
559# ensures any occurrences of the encodings themselves in the package/target
560# aren't clobbered by this translation, and one that does the encoding itself.
561# We also include a rule that protects the clobbering-protection rules from
562# getting clobbered.
Brian Silverman5f6f2762022-08-13 19:30:05 -0700563_substitutions = [("_z", "_zz_")] + [
Brian Silvermancc09f182022-03-09 15:40:20 -0800564 subst
565 for (pattern, replacement) in _encodings
566 for subst in (
Brian Silverman5f6f2762022-08-13 19:30:05 -0700567 ("_{}_".format(replacement), "_z{}_".format(replacement)),
Brian Silvermancc09f182022-03-09 15:40:20 -0800568 (pattern, "_{}_".format(replacement)),
569 )
570]
571
Brian Silverman5f6f2762022-08-13 19:30:05 -0700572# Expose the substitutions for testing only.
573substitutions_for_testing = _substitutions
574
Brian Silvermancc09f182022-03-09 15:40:20 -0800575def encode_label_as_crate_name(package, name):
576 """Encodes the package and target names in a format suitable for a crate name.
577
578 Args:
579 package (string): The package of the target in question.
580 name (string): The name of the target in question.
581
582 Returns:
583 A string that encodes the package and target name, to be used as the crate's name.
584 """
Brian Silverman5f6f2762022-08-13 19:30:05 -0700585 return _encode_raw_string(package + ":" + name)
586
587def _encode_raw_string(str):
588 """Encodes a string using the above encoding format.
589
590 Args:
591 str (string): The string to be encoded.
592
593 Returns:
594 An encoded version of the input string.
595 """
596 return _replace_all(str, _substitutions)
597
598# Expose the underlying encoding function for testing only.
599encode_raw_string_for_testing = _encode_raw_string
Brian Silvermancc09f182022-03-09 15:40:20 -0800600
601def decode_crate_name_as_label_for_testing(crate_name):
602 """Decodes a crate_name that was encoded by encode_label_as_crate_name.
603
604 This is used to check that the encoding is bijective; it is expected to only
605 be used in tests.
606
607 Args:
608 crate_name (string): The name of the crate.
609
610 Returns:
611 A string representing the Bazel label (package and target).
612 """
613 return _replace_all(crate_name, [(t[1], t[0]) for t in _substitutions])
614
615def _replace_all(string, substitutions):
616 """Replaces occurrences of the given patterns in `string`.
617
618 There are a few reasons this looks complicated:
619 * The substitutions are performed with some priority, i.e. patterns that are
620 listed first in `substitutions` are higher priority than patterns that are
621 listed later.
622 * We also take pains to avoid doing replacements that overlap with each
623 other, since overlaps invalidate pattern matches.
624 * To avoid hairy offset invalidation, we apply the substitutions
625 right-to-left.
626 * To avoid the "_quote" -> "_quotequote_" rule introducing new pattern
627 matches later in the string during decoding, we take the leftmost
628 replacement, in cases of overlap. (Note that no rule can induce new
629 pattern matches *earlier* in the string.) (E.g. "_quotedot_" encodes to
630 "_quotequote_dot_". Note that "_quotequote_" and "_dot_" both occur in
631 this string, and overlap.).
632
633 Args:
634 string (string): the string in which the replacements should be performed.
635 substitutions: the list of patterns and replacements to apply.
636
637 Returns:
638 A string with the appropriate substitutions performed.
639 """
640
641 # Find the highest-priority pattern matches for each string index, going
642 # left-to-right and skipping indices that are already involved in a
643 # pattern match.
644 plan = {}
645 matched_indices_set = {}
646 for pattern_start in range(len(string)):
647 if pattern_start in matched_indices_set:
648 continue
649 for (pattern, replacement) in substitutions:
650 if not string.startswith(pattern, pattern_start):
651 continue
652 length = len(pattern)
653 plan[pattern_start] = (length, replacement)
654 matched_indices_set.update([(pattern_start + i, True) for i in range(length)])
655 break
656
657 # Execute the replacement plan, working from right to left.
658 for pattern_start in sorted(plan.keys(), reverse = True):
659 length, replacement = plan[pattern_start]
660 after_pattern = pattern_start + length
661 string = string[:pattern_start] + replacement + string[after_pattern:]
662
663 return string
Brian Silverman5f6f2762022-08-13 19:30:05 -0700664
665def can_build_metadata(toolchain, ctx, crate_type):
666 """Can we build metadata for this rust_library?
667
668 Args:
669 toolchain (toolchain): The rust toolchain
670 ctx (ctx): The rule's context object
671 crate_type (String): one of lib|rlib|dylib|staticlib|cdylib|proc-macro
672
673 Returns:
674 bool: whether we can build metadata for this rule.
675 """
676
677 # In order to enable pipelined compilation we require that:
678 # 1) The _pipelined_compilation flag is enabled,
679 # 2) the OS running the rule is something other than windows as we require sandboxing (for now),
680 # 3) process_wrapper is enabled (this is disabled when compiling process_wrapper itself),
681 # 4) the crate_type is rlib or lib.
682 return toolchain._pipelined_compilation and \
Adam Snaider1c095c92023-07-08 02:09:58 -0400683 toolchain.exec_triple.system != "windows" and \
Brian Silverman5f6f2762022-08-13 19:30:05 -0700684 ctx.attr._process_wrapper and \
685 crate_type in ("rlib", "lib")
Adam Snaider1c095c92023-07-08 02:09:58 -0400686
687def crate_root_src(name, srcs, crate_type):
688 """Determines the source file for the crate root, should it not be specified in `attr.crate_root`.
689
690 Args:
691 name (str): The name of the target.
692 srcs (list): A list of all sources for the target Crate.
693 crate_type (str): The type of this crate ("bin", "lib", "rlib", "cdylib", etc).
694
695 Returns:
696 File: The root File object for a given crate. See the following links for more details:
697 - https://doc.rust-lang.org/cargo/reference/cargo-targets.html#library
698 - https://doc.rust-lang.org/cargo/reference/cargo-targets.html#binaries
699 """
700 default_crate_root_filename = "main.rs" if crate_type == "bin" else "lib.rs"
701
702 crate_root = (
703 (srcs[0] if len(srcs) == 1 else None) or
704 _shortest_src_with_basename(srcs, default_crate_root_filename) or
705 _shortest_src_with_basename(srcs, name + ".rs")
706 )
707 if not crate_root:
708 file_names = [default_crate_root_filename, name + ".rs"]
709 fail("No {} source file found.".format(" or ".join(file_names)), "srcs")
710 return crate_root
711
712def _shortest_src_with_basename(srcs, basename):
713 """Finds the shortest among the paths in srcs that match the desired basename.
714
715 Args:
716 srcs (list): A list of File objects
717 basename (str): The target basename to match against.
718
719 Returns:
720 File: The File object with the shortest path that matches `basename`
721 """
722 shortest = None
723 for f in srcs:
724 if f.basename == basename:
725 if not shortest or len(f.dirname) < len(shortest.dirname):
726 shortest = f
727 return shortest