• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1"""Utility macros for use in rules_rust repository rules"""
2
3load("//rust:known_shas.bzl", "FILE_KEY_TO_SHA")
4load(
5    "//rust/platform:triple_mappings.bzl",
6    "system_to_binary_ext",
7    "system_to_dylib_ext",
8    "system_to_staticlib_ext",
9    "system_to_stdlib_linkflags",
10)
11load("//rust/private:common.bzl", "DEFAULT_NIGHTLY_ISO_DATE")
12
13DEFAULT_TOOLCHAIN_NAME_PREFIX = "toolchain_for"
14DEFAULT_STATIC_RUST_URL_TEMPLATES = ["https://static.rust-lang.org/dist/{}.tar.xz"]
15DEFAULT_NIGHTLY_VERSION = "nightly/{}".format(DEFAULT_NIGHTLY_ISO_DATE)
16DEFAULT_EXTRA_TARGET_TRIPLES = ["wasm32-unknown-unknown", "wasm32-wasi"]
17
18TINYJSON_KWARGS = dict(
19    name = "rules_rust_tinyjson",
20    sha256 = "9ab95735ea2c8fd51154d01e39cf13912a78071c2d89abc49a7ef102a7dd725a",
21    url = "https://crates.io/api/v1/crates/tinyjson/2.5.1/download",
22    strip_prefix = "tinyjson-2.5.1",
23    type = "tar.gz",
24    build_file = "@rules_rust//util/process_wrapper:BUILD.tinyjson.bazel",
25)
26
27_build_file_for_compiler_template = """\
28filegroup(
29    name = "rustc",
30    srcs = ["bin/rustc{binary_ext}"],
31    visibility = ["//visibility:public"],
32)
33
34filegroup(
35    name = "rustc_lib",
36    srcs = glob(
37        [
38            "bin/*{dylib_ext}",
39            "lib/*{dylib_ext}",
40            "lib/rustlib/{target_triple}/codegen-backends/*{dylib_ext}",
41            "lib/rustlib/{target_triple}/bin/rust-lld{binary_ext}",
42            "lib/rustlib/{target_triple}/lib/*{dylib_ext}",
43        ],
44        allow_empty = True,
45    ),
46    visibility = ["//visibility:public"],
47)
48
49filegroup(
50    name = "rustdoc",
51    srcs = ["bin/rustdoc{binary_ext}"],
52    visibility = ["//visibility:public"],
53)
54"""
55
56def BUILD_for_compiler(target_triple):
57    """Emits a BUILD file the compiler archive.
58
59    Args:
60        target_triple (str): The triple of the target platform
61
62    Returns:
63        str: The contents of a BUILD file
64    """
65    return _build_file_for_compiler_template.format(
66        binary_ext = system_to_binary_ext(target_triple.system),
67        staticlib_ext = system_to_staticlib_ext(target_triple.system),
68        dylib_ext = system_to_dylib_ext(target_triple.system),
69        target_triple = target_triple.str,
70    )
71
72_build_file_for_cargo_template = """\
73filegroup(
74    name = "cargo",
75    srcs = ["bin/cargo{binary_ext}"],
76    visibility = ["//visibility:public"],
77)"""
78
79def BUILD_for_cargo(target_triple):
80    """Emits a BUILD file the cargo archive.
81
82    Args:
83        target_triple (str): The triple of the target platform
84
85    Returns:
86        str: The contents of a BUILD file
87    """
88    return _build_file_for_cargo_template.format(
89        binary_ext = system_to_binary_ext(target_triple.system),
90    )
91
92_build_file_for_rustfmt_template = """\
93filegroup(
94    name = "rustfmt_bin",
95    srcs = ["bin/rustfmt{binary_ext}"],
96    visibility = ["//visibility:public"],
97)
98
99sh_binary(
100    name = "rustfmt",
101    srcs = [":rustfmt_bin"],
102    visibility = ["//visibility:public"],
103)
104"""
105
106def BUILD_for_rustfmt(target_triple):
107    """Emits a BUILD file the rustfmt archive.
108
109    Args:
110        target_triple (str): The triple of the target platform
111
112    Returns:
113        str: The contents of a BUILD file
114    """
115    return _build_file_for_rustfmt_template.format(
116        binary_ext = system_to_binary_ext(target_triple.system),
117    )
118
119_build_file_for_clippy_template = """\
120filegroup(
121    name = "clippy_driver_bin",
122    srcs = ["bin/clippy-driver{binary_ext}"],
123    visibility = ["//visibility:public"],
124)
125"""
126
127_build_file_for_rust_analyzer_proc_macro_srv = """\
128filegroup(
129   name = "rust_analyzer_proc_macro_srv",
130   srcs = ["libexec/rust-analyzer-proc-macro-srv{binary_ext}"],
131   visibility = ["//visibility:public"],
132)
133"""
134
135def BUILD_for_rust_analyzer_proc_macro_srv(exec_triple):
136    """Emits a BUILD file the rust_analyzer_proc_macro_srv archive.
137
138    Args:
139        exec_triple (str): The triple of the exec platform
140    Returns:
141        str: The contents of a BUILD file
142    """
143    return _build_file_for_rust_analyzer_proc_macro_srv.format(
144        binary_ext = system_to_binary_ext(exec_triple.system),
145    )
146
147def BUILD_for_clippy(target_triple):
148    """Emits a BUILD file the clippy archive.
149
150    Args:
151        target_triple (str): The triple of the target platform
152
153    Returns:
154        str: The contents of a BUILD file
155    """
156    return _build_file_for_clippy_template.format(
157        binary_ext = system_to_binary_ext(target_triple.system),
158    )
159
160_build_file_for_llvm_tools = """\
161filegroup(
162    name = "llvm_cov_bin",
163    srcs = ["lib/rustlib/{target_triple}/bin/llvm-cov{binary_ext}"],
164    visibility = ["//visibility:public"],
165)
166
167filegroup(
168    name = "llvm_profdata_bin",
169    srcs = ["lib/rustlib/{target_triple}/bin/llvm-profdata{binary_ext}"],
170    visibility = ["//visibility:public"],
171)
172"""
173
174def BUILD_for_llvm_tools(target_triple):
175    """Emits a BUILD file the llvm-tools binaries.
176
177    Args:
178        target_triple (struct): The triple of the target platform
179
180    Returns:
181        str: The contents of a BUILD file
182    """
183    return _build_file_for_llvm_tools.format(
184        binary_ext = system_to_binary_ext(target_triple.system),
185        target_triple = target_triple.str,
186    )
187
188_build_file_for_stdlib_template = """\
189load("@rules_rust//rust:toolchain.bzl", "rust_stdlib_filegroup")
190
191rust_stdlib_filegroup(
192    name = "rust_std-{target_triple}",
193    srcs = glob(
194        [
195            "lib/rustlib/{target_triple}/lib/*.rlib",
196            "lib/rustlib/{target_triple}/lib/*{dylib_ext}",
197            "lib/rustlib/{target_triple}/lib/*{staticlib_ext}",
198            "lib/rustlib/{target_triple}/lib/self-contained/**",
199        ],
200        # Some patterns (e.g. `lib/*.a`) don't match anything, see https://github.com/bazelbuild/rules_rust/pull/245
201        allow_empty = True,
202    ),
203    visibility = ["//visibility:public"],
204)
205
206# For legacy support
207alias(
208    name = "rust_lib-{target_triple}",
209    actual = "rust_std-{target_triple}",
210    visibility = ["//visibility:public"],
211)
212"""
213
214def BUILD_for_stdlib(target_triple):
215    """Emits a BUILD file the stdlib archive.
216
217    Args:
218        target_triple (triple): The triple of the target platform
219
220    Returns:
221        str: The contents of a BUILD file
222    """
223    return _build_file_for_stdlib_template.format(
224        binary_ext = system_to_binary_ext(target_triple.system),
225        staticlib_ext = system_to_staticlib_ext(target_triple.system),
226        dylib_ext = system_to_dylib_ext(target_triple.system),
227        target_triple = target_triple.str,
228    )
229
230_build_file_for_rust_toolchain_template = """\
231load("@rules_rust//rust:toolchain.bzl", "rust_toolchain")
232
233rust_toolchain(
234    name = "{toolchain_name}",
235    rust_doc = "//:rustdoc",
236    rust_std = "//:rust_std-{target_triple}",
237    rustc = "//:rustc",
238    rustfmt = {rustfmt_label},
239    cargo = "//:cargo",
240    clippy_driver = "//:clippy_driver_bin",
241    llvm_cov = {llvm_cov_label},
242    llvm_profdata = {llvm_profdata_label},
243    rustc_lib = "//:rustc_lib",
244    allocator_library = {allocator_library},
245    global_allocator_library = {global_allocator_library},
246    binary_ext = "{binary_ext}",
247    staticlib_ext = "{staticlib_ext}",
248    dylib_ext = "{dylib_ext}",
249    stdlib_linkflags = [{stdlib_linkflags}],
250    default_edition = "{default_edition}",
251    exec_triple = "{exec_triple}",
252    target_triple = "{target_triple}",
253    visibility = ["//visibility:public"],
254    extra_rustc_flags = {extra_rustc_flags},
255    extra_exec_rustc_flags = {extra_exec_rustc_flags},
256    opt_level = {opt_level},
257)
258"""
259
260def BUILD_for_rust_toolchain(
261        name,
262        exec_triple,
263        target_triple,
264        allocator_library,
265        global_allocator_library,
266        default_edition,
267        include_rustfmt,
268        include_llvm_tools,
269        stdlib_linkflags = None,
270        extra_rustc_flags = None,
271        extra_exec_rustc_flags = None,
272        opt_level = None):
273    """Emits a toolchain declaration to match an existing compiler and stdlib.
274
275    Args:
276        name (str): The name of the toolchain declaration
277        exec_triple (triple): The rust-style target that this compiler runs on
278        target_triple (triple): The rust-style target triple of the tool
279        allocator_library (str, optional): Target that provides allocator functions when rust_library targets are embedded in a cc_binary.
280        global_allocator_library (str, optional): Target that provides allocator functions when a global allocator is used with cc_common_link.
281                                                  This target is only used in the target configuration; exec builds still use the symbols provided
282                                                  by the `allocator_library` target.
283        default_edition (str): Default Rust edition.
284        include_rustfmt (bool): Whether rustfmt is present in the toolchain.
285        include_llvm_tools (bool): Whether llvm-tools are present in the toolchain.
286        stdlib_linkflags (list, optional): Overriden flags needed for linking to rust
287                                           stdlib, akin to BAZEL_LINKLIBS. Defaults to
288                                           None.
289        extra_rustc_flags (list, optional): Extra flags to pass to rustc in non-exec configuration.
290        extra_exec_rustc_flags (list, optional): Extra flags to pass to rustc in exec configuration.
291        opt_level (dict, optional): Optimization level config for this toolchain.
292
293    Returns:
294        str: A rendered template of a `rust_toolchain` declaration
295    """
296    if stdlib_linkflags == None:
297        stdlib_linkflags = ", ".join(['"%s"' % x for x in system_to_stdlib_linkflags(target_triple.system)])
298
299    rustfmt_label = "None"
300    if include_rustfmt:
301        rustfmt_label = "\"//:rustfmt_bin\""
302    llvm_cov_label = "None"
303    llvm_profdata_label = "None"
304    if include_llvm_tools:
305        llvm_cov_label = "\"//:llvm_cov_bin\""
306        llvm_profdata_label = "\"//:llvm_profdata_bin\""
307    allocator_library_label = "None"
308    if allocator_library:
309        allocator_library_label = "\"{allocator_library}\"".format(allocator_library = allocator_library)
310    global_allocator_library_label = "None"
311    if global_allocator_library:
312        global_allocator_library_label = "\"{global_allocator_library}\"".format(global_allocator_library = global_allocator_library)
313
314    return _build_file_for_rust_toolchain_template.format(
315        toolchain_name = name,
316        binary_ext = system_to_binary_ext(target_triple.system),
317        staticlib_ext = system_to_staticlib_ext(target_triple.system),
318        dylib_ext = system_to_dylib_ext(target_triple.system),
319        allocator_library = allocator_library_label,
320        global_allocator_library = global_allocator_library_label,
321        stdlib_linkflags = stdlib_linkflags,
322        default_edition = default_edition,
323        exec_triple = exec_triple.str,
324        target_triple = target_triple.str,
325        rustfmt_label = rustfmt_label,
326        llvm_cov_label = llvm_cov_label,
327        llvm_profdata_label = llvm_profdata_label,
328        extra_rustc_flags = extra_rustc_flags,
329        extra_exec_rustc_flags = extra_exec_rustc_flags,
330        opt_level = opt_level,
331    )
332
333_build_file_for_toolchain_template = """\
334toolchain(
335    name = "{name}",
336    exec_compatible_with = {exec_constraint_sets_serialized},
337    target_compatible_with = {target_constraint_sets_serialized},
338    toolchain = "{toolchain}",
339    toolchain_type = "{toolchain_type}",
340    {target_settings}
341)
342"""
343
344def BUILD_for_toolchain(
345        name,
346        toolchain,
347        toolchain_type,
348        target_settings,
349        target_compatible_with,
350        exec_compatible_with):
351    target_settings_value = "target_settings = {},".format(json.encode(target_settings)) if target_settings else "# target_settings = []"
352
353    return _build_file_for_toolchain_template.format(
354        name = name,
355        exec_constraint_sets_serialized = json.encode(exec_compatible_with),
356        target_constraint_sets_serialized = json.encode(target_compatible_with),
357        toolchain = toolchain,
358        toolchain_type = toolchain_type,
359        target_settings = target_settings_value,
360    )
361
362def load_rustfmt(ctx, target_triple, version, iso_date):
363    """Loads a rustfmt binary and yields corresponding BUILD for it
364
365    Args:
366        ctx (repository_ctx): The repository rule's context object.
367        target_triple (struct): The platform triple to download rustfmt for.
368        version (str): The version or channel of rustfmt.
369        iso_date (str): The date of the tool (or None, if the version is a specific version).
370
371    Returns:
372        str: The BUILD file contents for this rustfmt binary
373    """
374
375    load_arbitrary_tool(
376        ctx,
377        iso_date = iso_date,
378        target_triple = target_triple,
379        tool_name = "rustfmt",
380        tool_subdirectories = ["rustfmt-preview"],
381        version = version,
382    )
383
384    return BUILD_for_rustfmt(target_triple)
385
386def load_rust_compiler(ctx, iso_date, target_triple, version):
387    """Loads a rust compiler and yields corresponding BUILD for it
388
389    Args:
390        ctx (repository_ctx): A repository_ctx.
391        iso_date (str): The date of the tool (or None, if the version is a specific version).
392        target_triple (struct): The Rust-style target that this compiler runs on.
393        version (str): The version of the tool among \"nightly\", \"beta\", or an exact version.
394
395    Returns:
396        str: The BUILD file contents for this compiler and compiler library
397    """
398
399    load_arbitrary_tool(
400        ctx,
401        iso_date = iso_date,
402        target_triple = target_triple,
403        tool_name = "rustc",
404        tool_subdirectories = ["rustc"],
405        version = version,
406    )
407
408    return BUILD_for_compiler(target_triple)
409
410def load_clippy(ctx, iso_date, target_triple, version):
411    """Loads Clippy and yields corresponding BUILD for it
412
413    Args:
414        ctx (repository_ctx): A repository_ctx.
415        iso_date (str): The date of the tool (or None, if the version is a specific version).
416        target_triple (struct): The Rust-style target that this compiler runs on.
417        version (str): The version of the tool among \"nightly\", \"beta\", or an exact version.
418
419    Returns:
420        str: The BUILD file contents for Clippy
421    """
422    load_arbitrary_tool(
423        ctx,
424        iso_date = iso_date,
425        target_triple = target_triple,
426        tool_name = "clippy",
427        tool_subdirectories = ["clippy-preview"],
428        version = version,
429    )
430
431    return BUILD_for_clippy(target_triple)
432
433def load_cargo(ctx, iso_date, target_triple, version):
434    """Loads Cargo and yields corresponding BUILD for it
435
436    Args:
437        ctx (repository_ctx): A repository_ctx.
438        iso_date (str): The date of the tool (or None, if the version is a specific version).
439        target_triple (struct): The Rust-style target that this compiler runs on.
440        version (str): The version of the tool among \"nightly\", \"beta\", or an exact version.
441
442    Returns:
443        str: The BUILD file contents for Cargo
444    """
445
446    load_arbitrary_tool(
447        ctx,
448        iso_date = iso_date,
449        target_triple = target_triple,
450        tool_name = "cargo",
451        tool_subdirectories = ["cargo"],
452        version = version,
453    )
454
455    return BUILD_for_cargo(target_triple)
456
457def includes_rust_analyzer_proc_macro_srv(version, iso_date):
458    """Determine whether or not the rust_analyzer_proc_macro_srv binary in available in the given version of Rust.
459
460    Args:
461        version (str): The version of the tool among \"nightly\", \"beta\", or an exact version.
462        iso_date (str): The date of the tool (or None, if the version is a specific version).
463
464    Returns:
465        bool: Whether or not the binary is expected to be included
466    """
467
468    if version == "nightly":
469        return iso_date >= "2022-09-21"
470    elif version == "beta":
471        return False
472    elif version >= "1.64.0":
473        return True
474
475    return False
476
477def load_rust_src(ctx, iso_date, version, sha256 = ""):
478    """Loads the rust source code. Used by the rust-analyzer rust-project.json generator.
479
480    Args:
481        ctx (ctx): A repository_ctx.
482        version (str): The version of the tool among "nightly", "beta', or an exact version.
483        iso_date (str): The date of the tool (or None, if the version is a specific version).
484        sha256 (str): The sha256 value for the `rust-src` artifact
485    """
486    tool_suburl = produce_tool_suburl("rust-src", None, version, iso_date)
487    url = ctx.attr.urls[0].format(tool_suburl)
488
489    tool_path = produce_tool_path("rust-src", version, None)
490    archive_path = tool_path + _get_tool_extension(getattr(ctx.attr, "urls", None))
491    sha256 = sha256 or getattr(ctx.attr, "sha256s", {}).get(archive_path) or FILE_KEY_TO_SHA.get(archive_path) or ""
492    ctx.download_and_extract(
493        url,
494        output = "lib/rustlib/src",
495        sha256 = sha256,
496        auth = _make_auth_dict(ctx, [url]),
497        stripPrefix = "{}/rust-src/lib/rustlib/src/rust".format(tool_path),
498    )
499    ctx.file(
500        "lib/rustlib/src/BUILD.bazel",
501        """\
502filegroup(
503    name = "rustc_srcs",
504    srcs = glob(["**/*"]),
505    visibility = ["//visibility:public"],
506)""",
507    )
508
509_build_file_for_rust_analyzer_toolchain_template = """\
510load("@rules_rust//rust:toolchain.bzl", "rust_analyzer_toolchain")
511
512rust_analyzer_toolchain(
513    name = "{name}",
514    proc_macro_srv = {proc_macro_srv},
515    rustc = "{rustc}",
516    rustc_srcs = "//lib/rustlib/src:rustc_srcs",
517    visibility = ["//visibility:public"],
518)
519"""
520
521def BUILD_for_rust_analyzer_toolchain(name, rustc, proc_macro_srv):
522    return _build_file_for_rust_analyzer_toolchain_template.format(
523        name = name,
524        rustc = rustc,
525        proc_macro_srv = repr(proc_macro_srv),
526    )
527
528_build_file_for_rustfmt_toolchain_template = """\
529load("@rules_rust//rust:toolchain.bzl", "rustfmt_toolchain")
530
531rustfmt_toolchain(
532    name = "{name}",
533    rustfmt = "{rustfmt}",
534    rustc = "{rustc}",
535    rustc_lib = "{rustc_lib}",
536    visibility = ["//visibility:public"],
537)
538"""
539
540def BUILD_for_rustfmt_toolchain(name, rustfmt, rustc, rustc_lib):
541    return _build_file_for_rustfmt_toolchain_template.format(
542        name = name,
543        rustfmt = rustfmt,
544        rustc = rustc,
545        rustc_lib = rustc_lib,
546    )
547
548def load_rust_stdlib(ctx, target_triple):
549    """Loads a rust standard library and yields corresponding BUILD for it
550
551    Args:
552        ctx (repository_ctx): A repository_ctx.
553        target_triple (struct): The rust-style target triple of the tool
554
555    Returns:
556        str: The BUILD file contents for this stdlib
557    """
558
559    load_arbitrary_tool(
560        ctx,
561        iso_date = ctx.attr.iso_date,
562        target_triple = target_triple,
563        tool_name = "rust-std",
564        tool_subdirectories = ["rust-std-{}".format(target_triple.str)],
565        version = ctx.attr.version,
566    )
567
568    return BUILD_for_stdlib(target_triple)
569
570def load_rustc_dev_nightly(ctx, target_triple):
571    """Loads the nightly rustc dev component
572
573    Args:
574        ctx: A repository_ctx.
575        target_triple: The rust-style target triple of the tool
576    """
577
578    subdir_name = "rustc-dev"
579    if ctx.attr.iso_date < "2020-12-24":
580        subdir_name = "rustc-dev-{}".format(target_triple)
581
582    load_arbitrary_tool(
583        ctx,
584        iso_date = ctx.attr.iso_date,
585        target_triple = target_triple,
586        tool_name = "rustc-dev",
587        tool_subdirectories = [subdir_name],
588        version = ctx.attr.version,
589    )
590
591def load_llvm_tools(ctx, target_triple):
592    """Loads the llvm tools
593
594    Args:
595        ctx: A repository_ctx.
596        target_triple: The rust-style target triple of the tool
597    """
598    load_arbitrary_tool(
599        ctx,
600        iso_date = ctx.attr.iso_date,
601        target_triple = target_triple,
602        tool_name = "llvm-tools",
603        tool_subdirectories = ["llvm-tools-preview"],
604        version = ctx.attr.version,
605    )
606
607    return BUILD_for_llvm_tools(target_triple)
608
609def check_version_valid(version, iso_date, param_prefix = ""):
610    """Verifies that the provided rust version and iso_date make sense.
611
612    Args:
613        version (str): The rustc version
614        iso_date (str): The rustc nightly version's iso date
615        param_prefix (str, optional): The name of the tool who's version is being checked.
616    """
617
618    if not version and iso_date:
619        fail("{param_prefix}iso_date must be paired with a {param_prefix}version".format(param_prefix = param_prefix))
620
621    if version in ("beta", "nightly") and not iso_date:
622        fail("{param_prefix}iso_date must be specified if version is 'beta' or 'nightly'".format(param_prefix = param_prefix))
623
624def produce_tool_suburl(tool_name, target_triple, version, iso_date = None):
625    """Produces a fully qualified Rust tool name for URL
626
627    Args:
628        tool_name (str): The name of the tool per `static.rust-lang.org`.
629        target_triple (struct): The rust-style target triple of the tool.
630        version (str): The version of the tool among "nightly", "beta', or an exact version.
631        iso_date (str): The date of the tool (or None, if the version is a specific version).
632
633    Returns:
634        str: The fully qualified url path for the specified tool.
635    """
636    path = produce_tool_path(tool_name, version, target_triple)
637    return iso_date + "/" + path if (iso_date and version in ("beta", "nightly")) else path
638
639def produce_tool_path(tool_name, version, target_triple = None):
640    """Produces a qualified Rust tool name
641
642    Args:
643        tool_name (str): The name of the tool per static.rust-lang.org
644        version (str): The version of the tool among "nightly", "beta', or an exact version.
645        target_triple (struct, optional): The rust-style target triple of the tool
646
647    Returns:
648        str: The qualified path for the specified tool.
649    """
650    if not tool_name:
651        fail("No tool name was provided")
652    if not version:
653        fail("No tool version was provided")
654
655    # Not all tools require a triple. E.g. `rustc_src` (Rust source files for rust-analyzer).
656    platform_triple = None
657    if target_triple:
658        platform_triple = target_triple.str
659
660    return "-".join([e for e in [tool_name, version, platform_triple] if e])
661
662def lookup_tool_sha256(ctx, tool_name, target_triple, version, iso_date, sha256):
663    """Looks up the sha256 hash of a specific tool archive.
664
665    The lookup order is:
666
667    1. The sha256s dict in the context attributes;
668    2. The list of sha256 hashes populated in //rust:known_shas.bzl;
669    3. The sha256 argument to the function
670
671    Args:
672        ctx (repository_ctx): A repository_ctx (no attrs required).
673        tool_name (str): The name of the given tool per the archive naming.
674        target_triple (struct): The rust-style target triple of the tool.
675        version (str): The version of the tool among "nightly", "beta', or an exact version.
676        iso_date (str): The date of the tool (ignored if the version is a specific version).
677        sha256 (str): The expected hash of hash of the Rust tool.
678
679    Returns:
680        str: The sha256 of the tool archive, or an empty string if the hash could not be found.
681    """
682    tool_suburl = produce_tool_suburl(tool_name, target_triple, version, iso_date)
683    archive_path = tool_suburl + _get_tool_extension(getattr(ctx.attr, "urls", None))
684    return getattr(ctx.attr, "sha256s", dict()).get(archive_path) or FILE_KEY_TO_SHA.get(archive_path) or sha256
685
686def load_arbitrary_tool(ctx, tool_name, tool_subdirectories, version, iso_date, target_triple, sha256 = ""):
687    """Loads a Rust tool, downloads, and extracts into the common workspace.
688
689    This function sources the tool from the Rust-lang static file server. The index is available at:
690    - https://static.rust-lang.org/dist/channel-rust-stable.toml
691    - https://static.rust-lang.org/dist/channel-rust-beta.toml
692    - https://static.rust-lang.org/dist/channel-rust-nightly.toml
693
694    Args:
695        ctx (repository_ctx): A repository_ctx (no attrs required).
696        tool_name (str): The name of the given tool per the archive naming.
697        tool_subdirectories (str): The subdirectories of the tool files (at a level below the root directory of
698            the archive). The root directory of the archive is expected to match
699            $TOOL_NAME-$VERSION-$TARGET_TRIPLE.
700            Example:
701            tool_name
702            |    version
703            |    |      target_triple
704            v    v      v
705            rust-1.39.0-x86_64-unknown-linux-gnu/clippy-preview
706                                             .../rustc
707                                             .../etc
708            tool_subdirectories = ["clippy-preview", "rustc"]
709        version (str): The version of the tool among "nightly", "beta', or an exact version.
710        iso_date (str): The date of the tool (ignored if the version is a specific version).
711        target_triple (struct): The rust-style target triple of the tool.
712        sha256 (str, optional): The expected hash of hash of the Rust tool. Defaults to "".
713    """
714    check_version_valid(version, iso_date, param_prefix = tool_name + "_")
715
716    # View the indices mentioned in the docstring to find the tool_suburl for a given
717    # tool.
718    tool_suburl = produce_tool_suburl(tool_name, target_triple, version, iso_date)
719    urls = []
720
721    for url in getattr(ctx.attr, "urls", DEFAULT_STATIC_RUST_URL_TEMPLATES):
722        new_url = url.format(tool_suburl)
723        if new_url not in urls:
724            urls.append(new_url)
725
726    tool_path = produce_tool_path(tool_name, version, target_triple)
727
728    sha256 = lookup_tool_sha256(ctx, tool_name, target_triple, version, iso_date, sha256)
729
730    for subdirectory in tool_subdirectories:
731        # As long as the sha256 value is consistent accross calls here the
732        # cost of downloading an artifact is negated as by Bazel's caching.
733        result = ctx.download_and_extract(
734            urls,
735            sha256 = sha256,
736            auth = _make_auth_dict(ctx, urls),
737            stripPrefix = "{}/{}".format(tool_path, subdirectory),
738        )
739
740        # In the event no sha256 was provided, set it to the value of the first
741        # downloaded item so subsequent downloads use a cached artifact.
742        if not sha256:
743            sha256 = result.sha256
744
745def _make_auth_dict(ctx, urls):
746    auth = getattr(ctx.attr, "auth", {})
747    if not auth:
748        return {}
749    ret = {}
750    for url in urls:
751        ret[url] = auth
752    return ret
753
754def _get_tool_extension(urls = None):
755    if urls == None:
756        urls = DEFAULT_STATIC_RUST_URL_TEMPLATES
757    if urls[0][-7:] == ".tar.gz":
758        return ".tar.gz"
759    elif urls[0][-7:] == ".tar.xz":
760        return ".tar.xz"
761    else:
762        return ""
763
764def select_rust_version(versions):
765    """Select the highest priorty version for a list of Rust versions
766
767    Priority order: `stable > nightly > beta`
768
769    Note that duplicate channels are unexpected in `versions`.
770
771    Args:
772        versions (list): A list of Rust versions. E.g. [`1.66.0`, `nightly/2022-12-15`]
773
774    Returns:
775        str: The highest ranking value from `versions`
776    """
777    if not versions:
778        fail("No versions were provided")
779
780    current = versions[0]
781
782    for ver in versions:
783        if ver.startswith("beta"):
784            if current[0].isdigit() or current.startswith("nightly"):
785                continue
786            if current.startswith("beta") and ver > current:
787                current = ver
788                continue
789
790            current = ver
791        elif ver.startswith("nightly"):
792            if current[0].isdigit():
793                continue
794            if current.startswith("nightly") and ver > current:
795                current = ver
796                continue
797
798            current = ver
799
800        else:
801            current = ver
802
803    return current
804
805_build_file_for_toolchain_hub_template = """
806toolchain(
807    name = "{name}",
808    exec_compatible_with = {exec_constraint_sets_serialized},
809    target_compatible_with = {target_constraint_sets_serialized},
810    toolchain = "{toolchain}",
811    toolchain_type = "{toolchain_type}",
812    visibility = ["//visibility:public"],
813)
814"""
815
816def BUILD_for_toolchain_hub(
817        toolchain_names,
818        toolchain_labels,
819        toolchain_types,
820        target_compatible_with,
821        exec_compatible_with):
822    return "\n".join([_build_file_for_toolchain_hub_template.format(
823        name = toolchain_name,
824        exec_constraint_sets_serialized = json.encode(exec_compatible_with[toolchain_name]),
825        target_constraint_sets_serialized = json.encode(target_compatible_with[toolchain_name]),
826        toolchain = toolchain_labels[toolchain_name],
827        toolchain_type = toolchain_types[toolchain_name],
828    ) for toolchain_name in toolchain_names])
829
830def _toolchain_repository_hub_impl(repository_ctx):
831    repository_ctx.file("WORKSPACE.bazel", """workspace(name = "{}")""".format(
832        repository_ctx.name,
833    ))
834
835    repository_ctx.file("BUILD.bazel", BUILD_for_toolchain_hub(
836        toolchain_names = repository_ctx.attr.toolchain_names,
837        toolchain_labels = repository_ctx.attr.toolchain_labels,
838        toolchain_types = repository_ctx.attr.toolchain_types,
839        target_compatible_with = repository_ctx.attr.target_compatible_with,
840        exec_compatible_with = repository_ctx.attr.exec_compatible_with,
841    ))
842
843toolchain_repository_hub = repository_rule(
844    doc = (
845        "Generates a toolchain-bearing repository that declares a set of other toolchains from other " +
846        "repositories. This exists to allow registering a set of toolchains in one go with the `:all` target."
847    ),
848    attrs = {
849        "exec_compatible_with": attr.string_list_dict(
850            doc = "A list of constraints for the execution platform for this toolchain, keyed by toolchain name.",
851            mandatory = True,
852        ),
853        "target_compatible_with": attr.string_list_dict(
854            doc = "A list of constraints for the target platform for this toolchain, keyed by toolchain name.",
855            mandatory = True,
856        ),
857        "toolchain_labels": attr.string_dict(
858            doc = "The name of the toolchain implementation target, keyed by toolchain name.",
859            mandatory = True,
860        ),
861        "toolchain_names": attr.string_list(mandatory = True),
862        "toolchain_types": attr.string_dict(
863            doc = "The toolchain type of the toolchain to declare, keyed by toolchain name.",
864            mandatory = True,
865        ),
866    },
867    implementation = _toolchain_repository_hub_impl,
868)
869