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