xref: /aosp_15_r20/external/bazelbuild-rules_rust/crate_universe/extension.bzl (revision d4726bddaa87cc4778e7472feed243fa4b6c267f)
1*d4726bddSHONG Yifan"""Module extension for generating third-party crates for use in bazel."""
2*d4726bddSHONG Yifan
3*d4726bddSHONG Yifanload("@bazel_features//:features.bzl", "bazel_features")
4*d4726bddSHONG Yifanload("@bazel_skylib//lib:structs.bzl", "structs")
5*d4726bddSHONG Yifanload("@bazel_tools//tools/build_defs/repo:git.bzl", "new_git_repository")
6*d4726bddSHONG Yifanload("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
7*d4726bddSHONG Yifanload("//crate_universe:defs.bzl", _crate_universe_crate = "crate")
8*d4726bddSHONG Yifanload("//crate_universe/private:crates_vendor.bzl", "CRATES_VENDOR_ATTRS", "generate_config_file", "generate_splicing_manifest")
9*d4726bddSHONG Yifanload("//crate_universe/private:generate_utils.bzl", "CARGO_BAZEL_GENERATOR_SHA256", "CARGO_BAZEL_GENERATOR_URL", "GENERATOR_ENV_VARS", "render_config")
10*d4726bddSHONG Yifanload("//crate_universe/private:urls.bzl", "CARGO_BAZEL_SHA256S", "CARGO_BAZEL_URLS")
11*d4726bddSHONG Yifanload("//crate_universe/private/module_extensions:cargo_bazel_bootstrap.bzl", "get_cargo_bazel_runner", "get_host_cargo_rustc")
12*d4726bddSHONG Yifanload("//rust/platform:triple.bzl", "get_host_triple")
13*d4726bddSHONG Yifan
14*d4726bddSHONG Yifan# A list of labels which may be relative (and if so, is within the repo the rule is generated in).
15*d4726bddSHONG Yifan#
16*d4726bddSHONG Yifan# If I were to write ":foo", with attr.label_list, it would evaluate to
17*d4726bddSHONG Yifan# "@@//:foo". However, for a tag such as deps, ":foo" should refer to
18*d4726bddSHONG Yifan# "@@rules_rust~crates~<crate>//:foo".
19*d4726bddSHONG Yifan_relative_label_list = attr.string_list
20*d4726bddSHONG Yifan
21*d4726bddSHONG Yifan_OPT_BOOL_VALUES = {
22*d4726bddSHONG Yifan    "auto": None,
23*d4726bddSHONG Yifan    "off": False,
24*d4726bddSHONG Yifan    "on": True,
25*d4726bddSHONG Yifan}
26*d4726bddSHONG Yifan
27*d4726bddSHONG Yifandef optional_bool(doc):
28*d4726bddSHONG Yifan    return attr.string(
29*d4726bddSHONG Yifan        doc = doc,
30*d4726bddSHONG Yifan        values = _OPT_BOOL_VALUES.keys(),
31*d4726bddSHONG Yifan        default = "auto",
32*d4726bddSHONG Yifan    )
33*d4726bddSHONG Yifan
34*d4726bddSHONG Yifandef _get_or_insert(d, key, value):
35*d4726bddSHONG Yifan    if key not in d:
36*d4726bddSHONG Yifan        d[key] = value
37*d4726bddSHONG Yifan    return d[key]
38*d4726bddSHONG Yifan
39*d4726bddSHONG Yifandef _generate_repo_impl(repo_ctx):
40*d4726bddSHONG Yifan    for path, contents in repo_ctx.attr.contents.items():
41*d4726bddSHONG Yifan        repo_ctx.file(path, contents)
42*d4726bddSHONG Yifan
43*d4726bddSHONG Yifan_generate_repo = repository_rule(
44*d4726bddSHONG Yifan    implementation = _generate_repo_impl,
45*d4726bddSHONG Yifan    attrs = dict(
46*d4726bddSHONG Yifan        contents = attr.string_dict(mandatory = True),
47*d4726bddSHONG Yifan    ),
48*d4726bddSHONG Yifan)
49*d4726bddSHONG Yifan
50*d4726bddSHONG Yifandef _annotations_for_repo(module_annotations, repo_specific_annotations):
51*d4726bddSHONG Yifan    """Merges the set of global annotations with the repo-specific ones
52*d4726bddSHONG Yifan
53*d4726bddSHONG Yifan    Args:
54*d4726bddSHONG Yifan        module_annotations (dict): The annotation tags that apply to all repos, keyed by crate.
55*d4726bddSHONG Yifan        repo_specific_annotations (dict): The annotation tags that apply to only this repo, keyed by crate.
56*d4726bddSHONG Yifan    """
57*d4726bddSHONG Yifan
58*d4726bddSHONG Yifan    if not repo_specific_annotations:
59*d4726bddSHONG Yifan        return module_annotations
60*d4726bddSHONG Yifan
61*d4726bddSHONG Yifan    annotations = dict(module_annotations)
62*d4726bddSHONG Yifan    for crate, values in repo_specific_annotations.items():
63*d4726bddSHONG Yifan        _get_or_insert(annotations, crate, []).extend(values)
64*d4726bddSHONG Yifan    return annotations
65*d4726bddSHONG Yifan
66*d4726bddSHONG Yifandef _generate_hub_and_spokes(module_ctx, cargo_bazel, cfg, annotations, cargo_lockfile = None, manifests = {}, packages = {}):
67*d4726bddSHONG Yifan    """Generates repositories for the transitive closure of crates defined by manifests and packages.
68*d4726bddSHONG Yifan
69*d4726bddSHONG Yifan    Args:
70*d4726bddSHONG Yifan        module_ctx (module_ctx): The module context object.
71*d4726bddSHONG Yifan        cargo_bazel (function): A function that can be called to execute cargo_bazel.
72*d4726bddSHONG Yifan        cfg (object): The module tag from `from_cargo` or `from_specs`
73*d4726bddSHONG Yifan        annotations (dict): The set of annotation tag classes that apply to this closure, keyed by crate name.
74*d4726bddSHONG Yifan        cargo_lockfile (path): Path to Cargo.lock, if we have one. This is optional for `from_specs` closures.
75*d4726bddSHONG Yifan        manifests (dict): The set of Cargo.toml manifests that apply to this closure, if any, keyed by path.
76*d4726bddSHONG Yifan        packages (dict): The set of extra cargo crate tags that apply to this closure, if any, keyed by package name.
77*d4726bddSHONG Yifan    """
78*d4726bddSHONG Yifan
79*d4726bddSHONG Yifan    tag_path = module_ctx.path(cfg.name)
80*d4726bddSHONG Yifan
81*d4726bddSHONG Yifan    rendering_config = json.decode(render_config(
82*d4726bddSHONG Yifan        regen_command = "Run 'cargo update [--workspace]'",
83*d4726bddSHONG Yifan    ))
84*d4726bddSHONG Yifan    config_file = tag_path.get_child("config.json")
85*d4726bddSHONG Yifan    module_ctx.file(
86*d4726bddSHONG Yifan        config_file,
87*d4726bddSHONG Yifan        executable = False,
88*d4726bddSHONG Yifan        content = generate_config_file(
89*d4726bddSHONG Yifan            module_ctx,
90*d4726bddSHONG Yifan            mode = "remote",
91*d4726bddSHONG Yifan            annotations = annotations,
92*d4726bddSHONG Yifan            generate_build_scripts = cfg.generate_build_scripts,
93*d4726bddSHONG Yifan            supported_platform_triples = cfg.supported_platform_triples,
94*d4726bddSHONG Yifan            generate_target_compatible_with = True,
95*d4726bddSHONG Yifan            repository_name = cfg.name,
96*d4726bddSHONG Yifan            output_pkg = cfg.name,
97*d4726bddSHONG Yifan            workspace_name = cfg.name,
98*d4726bddSHONG Yifan            generate_binaries = cfg.generate_binaries,
99*d4726bddSHONG Yifan            render_config = rendering_config,
100*d4726bddSHONG Yifan            repository_ctx = module_ctx,
101*d4726bddSHONG Yifan        ),
102*d4726bddSHONG Yifan    )
103*d4726bddSHONG Yifan
104*d4726bddSHONG Yifan    splicing_manifest = tag_path.get_child("splicing_manifest.json")
105*d4726bddSHONG Yifan    module_ctx.file(
106*d4726bddSHONG Yifan        splicing_manifest,
107*d4726bddSHONG Yifan        executable = False,
108*d4726bddSHONG Yifan        content = generate_splicing_manifest(
109*d4726bddSHONG Yifan            packages = packages,
110*d4726bddSHONG Yifan            splicing_config = "",
111*d4726bddSHONG Yifan            cargo_config = cfg.cargo_config,
112*d4726bddSHONG Yifan            manifests = manifests,
113*d4726bddSHONG Yifan            manifest_to_path = module_ctx.path,
114*d4726bddSHONG Yifan        ),
115*d4726bddSHONG Yifan    )
116*d4726bddSHONG Yifan
117*d4726bddSHONG Yifan    splicing_output_dir = tag_path.get_child("splicing-output")
118*d4726bddSHONG Yifan    splice_args = [
119*d4726bddSHONG Yifan        "splice",
120*d4726bddSHONG Yifan        "--output-dir",
121*d4726bddSHONG Yifan        splicing_output_dir,
122*d4726bddSHONG Yifan        "--config",
123*d4726bddSHONG Yifan        config_file,
124*d4726bddSHONG Yifan        "--splicing-manifest",
125*d4726bddSHONG Yifan        splicing_manifest,
126*d4726bddSHONG Yifan    ]
127*d4726bddSHONG Yifan    if cargo_lockfile:
128*d4726bddSHONG Yifan        splice_args.extend([
129*d4726bddSHONG Yifan            "--cargo-lockfile",
130*d4726bddSHONG Yifan            cargo_lockfile,
131*d4726bddSHONG Yifan        ])
132*d4726bddSHONG Yifan    cargo_bazel(splice_args)
133*d4726bddSHONG Yifan
134*d4726bddSHONG Yifan    # Create a lockfile, since we need to parse it to generate spoke
135*d4726bddSHONG Yifan    # repos.
136*d4726bddSHONG Yifan    lockfile_path = tag_path.get_child("lockfile.json")
137*d4726bddSHONG Yifan    module_ctx.file(lockfile_path, "")
138*d4726bddSHONG Yifan
139*d4726bddSHONG Yifan    cargo_bazel([
140*d4726bddSHONG Yifan        "generate",
141*d4726bddSHONG Yifan        "--cargo-lockfile",
142*d4726bddSHONG Yifan        cargo_lockfile or splicing_output_dir.get_child("Cargo.lock"),
143*d4726bddSHONG Yifan        "--config",
144*d4726bddSHONG Yifan        config_file,
145*d4726bddSHONG Yifan        "--splicing-manifest",
146*d4726bddSHONG Yifan        splicing_manifest,
147*d4726bddSHONG Yifan        "--repository-dir",
148*d4726bddSHONG Yifan        tag_path,
149*d4726bddSHONG Yifan        "--metadata",
150*d4726bddSHONG Yifan        splicing_output_dir.get_child("metadata.json"),
151*d4726bddSHONG Yifan        "--repin",
152*d4726bddSHONG Yifan        "--lockfile",
153*d4726bddSHONG Yifan        lockfile_path,
154*d4726bddSHONG Yifan    ])
155*d4726bddSHONG Yifan
156*d4726bddSHONG Yifan    crates_dir = tag_path.get_child(cfg.name)
157*d4726bddSHONG Yifan    _generate_repo(
158*d4726bddSHONG Yifan        name = cfg.name,
159*d4726bddSHONG Yifan        contents = {
160*d4726bddSHONG Yifan            "BUILD.bazel": module_ctx.read(crates_dir.get_child("BUILD.bazel")),
161*d4726bddSHONG Yifan            "defs.bzl": module_ctx.read(crates_dir.get_child("defs.bzl")),
162*d4726bddSHONG Yifan        },
163*d4726bddSHONG Yifan    )
164*d4726bddSHONG Yifan
165*d4726bddSHONG Yifan    contents = json.decode(module_ctx.read(lockfile_path))
166*d4726bddSHONG Yifan
167*d4726bddSHONG Yifan    for crate in contents["crates"].values():
168*d4726bddSHONG Yifan        repo = crate["repository"]
169*d4726bddSHONG Yifan        if repo == None:
170*d4726bddSHONG Yifan            continue
171*d4726bddSHONG Yifan        name = crate["name"]
172*d4726bddSHONG Yifan        version = crate["version"]
173*d4726bddSHONG Yifan
174*d4726bddSHONG Yifan        # "+" isn't valid in a repo name.
175*d4726bddSHONG Yifan        crate_repo_name = "{repo_name}__{name}-{version}".format(
176*d4726bddSHONG Yifan            repo_name = cfg.name,
177*d4726bddSHONG Yifan            name = name,
178*d4726bddSHONG Yifan            version = version.replace("+", "-"),
179*d4726bddSHONG Yifan        )
180*d4726bddSHONG Yifan
181*d4726bddSHONG Yifan        build_file_content = module_ctx.read(crates_dir.get_child("BUILD.%s-%s.bazel" % (name, version)))
182*d4726bddSHONG Yifan        if "Http" in repo:
183*d4726bddSHONG Yifan            # Replicates functionality in repo_http.j2.
184*d4726bddSHONG Yifan            repo = repo["Http"]
185*d4726bddSHONG Yifan            http_archive(
186*d4726bddSHONG Yifan                name = crate_repo_name,
187*d4726bddSHONG Yifan                patch_args = repo.get("patch_args", None),
188*d4726bddSHONG Yifan                patch_tool = repo.get("patch_tool", None),
189*d4726bddSHONG Yifan                patches = repo.get("patches", None),
190*d4726bddSHONG Yifan                remote_patch_strip = 1,
191*d4726bddSHONG Yifan                sha256 = repo.get("sha256", None),
192*d4726bddSHONG Yifan                type = "tar.gz",
193*d4726bddSHONG Yifan                urls = [repo["url"]],
194*d4726bddSHONG Yifan                strip_prefix = "%s-%s" % (crate["name"], crate["version"]),
195*d4726bddSHONG Yifan                build_file_content = build_file_content,
196*d4726bddSHONG Yifan            )
197*d4726bddSHONG Yifan        elif "Git" in repo:
198*d4726bddSHONG Yifan            # Replicates functionality in repo_git.j2
199*d4726bddSHONG Yifan            repo = repo["Git"]
200*d4726bddSHONG Yifan            kwargs = {}
201*d4726bddSHONG Yifan            for k, v in repo["commitish"].items():
202*d4726bddSHONG Yifan                if k == "Rev":
203*d4726bddSHONG Yifan                    kwargs["commit"] = v
204*d4726bddSHONG Yifan                else:
205*d4726bddSHONG Yifan                    kwargs[k.lower()] = v
206*d4726bddSHONG Yifan            new_git_repository(
207*d4726bddSHONG Yifan                name = crate_repo_name,
208*d4726bddSHONG Yifan                init_submodules = True,
209*d4726bddSHONG Yifan                patch_args = repo.get("patch_args", None),
210*d4726bddSHONG Yifan                patch_tool = repo.get("patch_tool", None),
211*d4726bddSHONG Yifan                patches = repo.get("patches", None),
212*d4726bddSHONG Yifan                shallow_since = repo.get("shallow_since", None),
213*d4726bddSHONG Yifan                remote = repo["remote"],
214*d4726bddSHONG Yifan                build_file_content = build_file_content,
215*d4726bddSHONG Yifan                strip_prefix = repo.get("strip_prefix", None),
216*d4726bddSHONG Yifan                **kwargs
217*d4726bddSHONG Yifan            )
218*d4726bddSHONG Yifan        else:
219*d4726bddSHONG Yifan            fail("Invalid repo: expected Http or Git to exist for crate %s-%s, got %s" % (name, version, repo))
220*d4726bddSHONG Yifan
221*d4726bddSHONG Yifandef _package_to_json(p):
222*d4726bddSHONG Yifan    # Avoid adding unspecified properties.
223*d4726bddSHONG Yifan    # If we add them as empty strings, cargo-bazel will be unhappy.
224*d4726bddSHONG Yifan    return json.encode({
225*d4726bddSHONG Yifan        k: v
226*d4726bddSHONG Yifan        for k, v in structs.to_dict(p).items()
227*d4726bddSHONG Yifan        if v or k == "default_features"
228*d4726bddSHONG Yifan    })
229*d4726bddSHONG Yifan
230*d4726bddSHONG Yifandef _get_generator(module_ctx):
231*d4726bddSHONG Yifan    """Query Network Resources to local a `cargo-bazel` binary.
232*d4726bddSHONG Yifan
233*d4726bddSHONG Yifan    Based off get_generator in crates_universe/private/generate_utils.bzl
234*d4726bddSHONG Yifan
235*d4726bddSHONG Yifan    Args:
236*d4726bddSHONG Yifan        module_ctx (module_ctx):  The rules context object
237*d4726bddSHONG Yifan
238*d4726bddSHONG Yifan    Returns:
239*d4726bddSHONG Yifan        tuple(path, dict) The path to a 'cargo-bazel' binary. The pairing (dict)
240*d4726bddSHONG Yifan            may be `None` if there is not need to update the attribute
241*d4726bddSHONG Yifan    """
242*d4726bddSHONG Yifan    host_triple = get_host_triple(module_ctx)
243*d4726bddSHONG Yifan    use_environ = False
244*d4726bddSHONG Yifan    for var in GENERATOR_ENV_VARS:
245*d4726bddSHONG Yifan        if var in module_ctx.os.environ:
246*d4726bddSHONG Yifan            use_environ = True
247*d4726bddSHONG Yifan
248*d4726bddSHONG Yifan    if use_environ:
249*d4726bddSHONG Yifan        generator_sha256 = module_ctx.os.environ.get(CARGO_BAZEL_GENERATOR_SHA256)
250*d4726bddSHONG Yifan        generator_url = module_ctx.os.environ.get(CARGO_BAZEL_GENERATOR_URL)
251*d4726bddSHONG Yifan    elif len(CARGO_BAZEL_URLS) == 0:
252*d4726bddSHONG Yifan        return module_ctx.path(Label("@cargo_bazel_bootstrap//:cargo-bazel"))
253*d4726bddSHONG Yifan    else:
254*d4726bddSHONG Yifan        generator_sha256 = CARGO_BAZEL_SHA256S.get(host_triple.str)
255*d4726bddSHONG Yifan        generator_url = CARGO_BAZEL_URLS.get(host_triple.str)
256*d4726bddSHONG Yifan
257*d4726bddSHONG Yifan    if not generator_url:
258*d4726bddSHONG Yifan        fail((
259*d4726bddSHONG Yifan            "No generator URL was found either in the `CARGO_BAZEL_GENERATOR_URL` " +
260*d4726bddSHONG Yifan            "environment variable or for the `{}` triple in the `generator_urls` attribute"
261*d4726bddSHONG Yifan        ).format(host_triple.str))
262*d4726bddSHONG Yifan
263*d4726bddSHONG Yifan    output = module_ctx.path("cargo-bazel.exe" if "win" in module_ctx.os.name else "cargo-bazel")
264*d4726bddSHONG Yifan
265*d4726bddSHONG Yifan    # Download the file into place
266*d4726bddSHONG Yifan    download_kwargs = {
267*d4726bddSHONG Yifan        "executable": True,
268*d4726bddSHONG Yifan        "output": output,
269*d4726bddSHONG Yifan        "url": generator_url,
270*d4726bddSHONG Yifan    }
271*d4726bddSHONG Yifan
272*d4726bddSHONG Yifan    if generator_sha256:
273*d4726bddSHONG Yifan        download_kwargs.update({"sha256": generator_sha256})
274*d4726bddSHONG Yifan
275*d4726bddSHONG Yifan    module_ctx.download(**download_kwargs)
276*d4726bddSHONG Yifan    return output
277*d4726bddSHONG Yifan
278*d4726bddSHONG Yifandef _crate_impl(module_ctx):
279*d4726bddSHONG Yifan    # Preload all external repositories. Calling `module_ctx.path` will cause restarts of the implementation
280*d4726bddSHONG Yifan    # function of the module extension, so we want to trigger all restarts before we start the actual work.
281*d4726bddSHONG Yifan    # Once https://github.com/bazelbuild/bazel/issues/22729 has been fixed, this code can be removed.
282*d4726bddSHONG Yifan    get_host_cargo_rustc(module_ctx)
283*d4726bddSHONG Yifan    for mod in module_ctx.modules:
284*d4726bddSHONG Yifan        for cfg in mod.tags.from_cargo:
285*d4726bddSHONG Yifan            module_ctx.path(cfg.cargo_lockfile)
286*d4726bddSHONG Yifan            for m in cfg.manifests:
287*d4726bddSHONG Yifan                module_ctx.path(m)
288*d4726bddSHONG Yifan
289*d4726bddSHONG Yifan    cargo_bazel_output = _get_generator(module_ctx)
290*d4726bddSHONG Yifan    cargo_bazel = get_cargo_bazel_runner(module_ctx, cargo_bazel_output)
291*d4726bddSHONG Yifan
292*d4726bddSHONG Yifan    all_repos = []
293*d4726bddSHONG Yifan    reproducible = True
294*d4726bddSHONG Yifan
295*d4726bddSHONG Yifan    for mod in module_ctx.modules:
296*d4726bddSHONG Yifan        module_annotations = {}
297*d4726bddSHONG Yifan        repo_specific_annotations = {}
298*d4726bddSHONG Yifan        for annotation_tag in mod.tags.annotation:
299*d4726bddSHONG Yifan            annotation_dict = structs.to_dict(annotation_tag)
300*d4726bddSHONG Yifan            repositories = annotation_dict.pop("repositories")
301*d4726bddSHONG Yifan            crate = annotation_dict.pop("crate")
302*d4726bddSHONG Yifan
303*d4726bddSHONG Yifan            # The crate.annotation function can take in either a list or a bool.
304*d4726bddSHONG Yifan            # For the tag-based method, because it has type safety, we have to
305*d4726bddSHONG Yifan            # split it into two parameters.
306*d4726bddSHONG Yifan            if annotation_dict.pop("gen_all_binaries"):
307*d4726bddSHONG Yifan                annotation_dict["gen_binaries"] = True
308*d4726bddSHONG Yifan            annotation_dict["gen_build_script"] = _OPT_BOOL_VALUES[annotation_dict["gen_build_script"]]
309*d4726bddSHONG Yifan
310*d4726bddSHONG Yifan            # Process the override targets for the annotation.
311*d4726bddSHONG Yifan            # In the non-bzlmod approach, this is given as a dict
312*d4726bddSHONG Yifan            # with the possible keys "`proc_macro`, `build_script`, `lib`, `bin`".
313*d4726bddSHONG Yifan            # With the tag-based approach used in Bzlmod, we run into an issue
314*d4726bddSHONG Yifan            # where there is no dict type that takes a string as a key and a Label as the value.
315*d4726bddSHONG Yifan            # To work around this, we split the override option into four, and reconstruct the
316*d4726bddSHONG Yifan            # dictionary here during processing
317*d4726bddSHONG Yifan            annotation_dict["override_targets"] = dict()
318*d4726bddSHONG Yifan            replacement = annotation_dict.pop("override_target_lib")
319*d4726bddSHONG Yifan            if replacement:
320*d4726bddSHONG Yifan                annotation_dict["override_targets"]["lib"] = str(replacement)
321*d4726bddSHONG Yifan
322*d4726bddSHONG Yifan            replacement = annotation_dict.pop("override_target_proc_macro")
323*d4726bddSHONG Yifan            if replacement:
324*d4726bddSHONG Yifan                annotation_dict["override_targets"]["proc_macro"] = str(replacement)
325*d4726bddSHONG Yifan
326*d4726bddSHONG Yifan            replacement = annotation_dict.pop("override_target_build_script")
327*d4726bddSHONG Yifan            if replacement:
328*d4726bddSHONG Yifan                annotation_dict["override_targets"]["build_script"] = str(replacement)
329*d4726bddSHONG Yifan
330*d4726bddSHONG Yifan            replacement = annotation_dict.pop("override_target_bin")
331*d4726bddSHONG Yifan            if replacement:
332*d4726bddSHONG Yifan                annotation_dict["override_targets"]["bin"] = str(replacement)
333*d4726bddSHONG Yifan
334*d4726bddSHONG Yifan            annotation = _crate_universe_crate.annotation(**{
335*d4726bddSHONG Yifan                k: v
336*d4726bddSHONG Yifan                for k, v in annotation_dict.items()
337*d4726bddSHONG Yifan                # Tag classes can't take in None, but the function requires None
338*d4726bddSHONG Yifan                # instead of the empty values in many cases.
339*d4726bddSHONG Yifan                # https://github.com/bazelbuild/bazel/issues/20744
340*d4726bddSHONG Yifan                if v != "" and v != [] and v != {}
341*d4726bddSHONG Yifan            })
342*d4726bddSHONG Yifan            if not repositories:
343*d4726bddSHONG Yifan                _get_or_insert(module_annotations, crate, []).append(annotation)
344*d4726bddSHONG Yifan            for repo in repositories:
345*d4726bddSHONG Yifan                _get_or_insert(
346*d4726bddSHONG Yifan                    _get_or_insert(repo_specific_annotations, repo, {}),
347*d4726bddSHONG Yifan                    crate,
348*d4726bddSHONG Yifan                    [],
349*d4726bddSHONG Yifan                ).append(annotation)
350*d4726bddSHONG Yifan
351*d4726bddSHONG Yifan        local_repos = []
352*d4726bddSHONG Yifan
353*d4726bddSHONG Yifan        for cfg in mod.tags.from_cargo + mod.tags.from_specs:
354*d4726bddSHONG Yifan            if cfg.name in local_repos:
355*d4726bddSHONG Yifan                fail("Defined two crate universes with the same name in the same MODULE.bazel file. Use the name tag to give them different names.")
356*d4726bddSHONG Yifan            elif cfg.name in all_repos:
357*d4726bddSHONG Yifan                fail("Defined two crate universes with the same name in different MODULE.bazel files. Either give one a different name, or use use_extension(isolate=True)")
358*d4726bddSHONG Yifan            all_repos.append(cfg.name)
359*d4726bddSHONG Yifan            local_repos.append(cfg.name)
360*d4726bddSHONG Yifan
361*d4726bddSHONG Yifan        for cfg in mod.tags.from_cargo:
362*d4726bddSHONG Yifan            annotations = _annotations_for_repo(
363*d4726bddSHONG Yifan                module_annotations,
364*d4726bddSHONG Yifan                repo_specific_annotations.get(cfg.name),
365*d4726bddSHONG Yifan            )
366*d4726bddSHONG Yifan
367*d4726bddSHONG Yifan            cargo_lockfile = module_ctx.path(cfg.cargo_lockfile)
368*d4726bddSHONG Yifan            manifests = {str(module_ctx.path(m)): str(m) for m in cfg.manifests}
369*d4726bddSHONG Yifan            _generate_hub_and_spokes(module_ctx, cargo_bazel, cfg, annotations, cargo_lockfile = cargo_lockfile, manifests = manifests)
370*d4726bddSHONG Yifan
371*d4726bddSHONG Yifan        for cfg in mod.tags.from_specs:
372*d4726bddSHONG Yifan            # We don't have a Cargo.lock so the resolution can change.
373*d4726bddSHONG Yifan            # We could maybe make this reproducible by using `-minimal-version` during resolution.
374*d4726bddSHONG Yifan            # See https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#minimal-versions
375*d4726bddSHONG Yifan            reproducible = False
376*d4726bddSHONG Yifan
377*d4726bddSHONG Yifan            annotations = _annotations_for_repo(
378*d4726bddSHONG Yifan                module_annotations,
379*d4726bddSHONG Yifan                repo_specific_annotations.get(cfg.name),
380*d4726bddSHONG Yifan            )
381*d4726bddSHONG Yifan
382*d4726bddSHONG Yifan            packages = {p.package: _package_to_json(p) for p in mod.tags.spec}
383*d4726bddSHONG Yifan            _generate_hub_and_spokes(module_ctx, cargo_bazel, cfg, annotations, packages = packages)
384*d4726bddSHONG Yifan
385*d4726bddSHONG Yifan        for repo in repo_specific_annotations:
386*d4726bddSHONG Yifan            if repo not in local_repos:
387*d4726bddSHONG Yifan                fail("Annotation specified for repo %s, but the module defined repositories %s" % (repo, local_repos))
388*d4726bddSHONG Yifan
389*d4726bddSHONG Yifan    metadata_kwargs = {}
390*d4726bddSHONG Yifan    if bazel_features.external_deps.extension_metadata_has_reproducible:
391*d4726bddSHONG Yifan        metadata_kwargs["reproducible"] = reproducible
392*d4726bddSHONG Yifan
393*d4726bddSHONG Yifan    return module_ctx.extension_metadata(**metadata_kwargs)
394*d4726bddSHONG Yifan
395*d4726bddSHONG Yifan_from_cargo = tag_class(
396*d4726bddSHONG Yifan    doc = "Generates a repo @crates from a Cargo.toml / Cargo.lock pair",
397*d4726bddSHONG Yifan    attrs = dict(
398*d4726bddSHONG Yifan        name = attr.string(
399*d4726bddSHONG Yifan            doc = "The name of the repo to generate",
400*d4726bddSHONG Yifan            default = "crates",
401*d4726bddSHONG Yifan        ),
402*d4726bddSHONG Yifan        cargo_lockfile = CRATES_VENDOR_ATTRS["cargo_lockfile"],
403*d4726bddSHONG Yifan        manifests = CRATES_VENDOR_ATTRS["manifests"],
404*d4726bddSHONG Yifan        cargo_config = CRATES_VENDOR_ATTRS["cargo_config"],
405*d4726bddSHONG Yifan        generate_binaries = CRATES_VENDOR_ATTRS["generate_binaries"],
406*d4726bddSHONG Yifan        generate_build_scripts = CRATES_VENDOR_ATTRS["generate_build_scripts"],
407*d4726bddSHONG Yifan        supported_platform_triples = CRATES_VENDOR_ATTRS["supported_platform_triples"],
408*d4726bddSHONG Yifan    ),
409*d4726bddSHONG Yifan)
410*d4726bddSHONG Yifan
411*d4726bddSHONG Yifan# This should be kept in sync with crate_universe/private/crate.bzl.
412*d4726bddSHONG Yifan_annotation = tag_class(
413*d4726bddSHONG Yifan    attrs = dict(
414*d4726bddSHONG Yifan        repositories = attr.string_list(
415*d4726bddSHONG Yifan            doc = "A list of repository names specified from `crate.from_cargo(name=...)` that this annotation is applied to. Defaults to all repositories.",
416*d4726bddSHONG Yifan            default = [],
417*d4726bddSHONG Yifan        ),
418*d4726bddSHONG Yifan        crate = attr.string(
419*d4726bddSHONG Yifan            doc = "The name of the crate the annotation is applied to",
420*d4726bddSHONG Yifan            mandatory = True,
421*d4726bddSHONG Yifan        ),
422*d4726bddSHONG Yifan        version = attr.string(
423*d4726bddSHONG Yifan            doc = "The versions of the crate the annotation is applied to. Defaults to all versions.",
424*d4726bddSHONG Yifan            default = "*",
425*d4726bddSHONG Yifan        ),
426*d4726bddSHONG Yifan        additive_build_file_content = attr.string(
427*d4726bddSHONG Yifan            doc = "Extra contents to write to the bottom of generated BUILD files.",
428*d4726bddSHONG Yifan        ),
429*d4726bddSHONG Yifan        additive_build_file = attr.label(
430*d4726bddSHONG Yifan            doc = "A file containing extra contents to write to the bottom of generated BUILD files.",
431*d4726bddSHONG Yifan        ),
432*d4726bddSHONG Yifan        alias_rule = attr.string(
433*d4726bddSHONG Yifan            doc = "Alias rule to use instead of `native.alias()`.  Overrides [render_config](#render_config)'s 'default_alias_rule'.",
434*d4726bddSHONG Yifan        ),
435*d4726bddSHONG Yifan        build_script_data = _relative_label_list(
436*d4726bddSHONG Yifan            doc = "A list of labels to add to a crate's `cargo_build_script::data` attribute.",
437*d4726bddSHONG Yifan        ),
438*d4726bddSHONG Yifan        build_script_tools = _relative_label_list(
439*d4726bddSHONG Yifan            doc = "A list of labels to add to a crate's `cargo_build_script::tools` attribute.",
440*d4726bddSHONG Yifan        ),
441*d4726bddSHONG Yifan        build_script_data_glob = attr.string_list(
442*d4726bddSHONG Yifan            doc = "A list of glob patterns to add to a crate's `cargo_build_script::data` attribute",
443*d4726bddSHONG Yifan        ),
444*d4726bddSHONG Yifan        build_script_deps = _relative_label_list(
445*d4726bddSHONG Yifan            doc = "A list of labels to add to a crate's `cargo_build_script::deps` attribute.",
446*d4726bddSHONG Yifan        ),
447*d4726bddSHONG Yifan        build_script_env = attr.string_dict(
448*d4726bddSHONG Yifan            doc = "Additional environment variables to set on a crate's `cargo_build_script::env` attribute.",
449*d4726bddSHONG Yifan        ),
450*d4726bddSHONG Yifan        build_script_proc_macro_deps = _relative_label_list(
451*d4726bddSHONG Yifan            doc = "A list of labels to add to a crate's `cargo_build_script::proc_macro_deps` attribute.",
452*d4726bddSHONG Yifan        ),
453*d4726bddSHONG Yifan        build_script_rundir = attr.string(
454*d4726bddSHONG Yifan            doc = "An override for the build script's rundir attribute.",
455*d4726bddSHONG Yifan        ),
456*d4726bddSHONG Yifan        build_script_rustc_env = attr.string_dict(
457*d4726bddSHONG Yifan            doc = "Additional environment variables to set on a crate's `cargo_build_script::env` attribute.",
458*d4726bddSHONG Yifan        ),
459*d4726bddSHONG Yifan        build_script_toolchains = attr.label_list(
460*d4726bddSHONG Yifan            doc = "A list of labels to set on a crates's `cargo_build_script::toolchains` attribute.",
461*d4726bddSHONG Yifan        ),
462*d4726bddSHONG Yifan        compile_data = _relative_label_list(
463*d4726bddSHONG Yifan            doc = "A list of labels to add to a crate's `rust_library::compile_data` attribute.",
464*d4726bddSHONG Yifan        ),
465*d4726bddSHONG Yifan        compile_data_glob = attr.string_list(
466*d4726bddSHONG Yifan            doc = "A list of glob patterns to add to a crate's `rust_library::compile_data` attribute.",
467*d4726bddSHONG Yifan        ),
468*d4726bddSHONG Yifan        crate_features = attr.string_list(
469*d4726bddSHONG Yifan            doc = "A list of strings to add to a crate's `rust_library::crate_features` attribute.",
470*d4726bddSHONG Yifan        ),
471*d4726bddSHONG Yifan        data = _relative_label_list(
472*d4726bddSHONG Yifan            doc = "A list of labels to add to a crate's `rust_library::data` attribute.",
473*d4726bddSHONG Yifan        ),
474*d4726bddSHONG Yifan        data_glob = attr.string_list(
475*d4726bddSHONG Yifan            doc = "A list of glob patterns to add to a crate's `rust_library::data` attribute.",
476*d4726bddSHONG Yifan        ),
477*d4726bddSHONG Yifan        deps = _relative_label_list(
478*d4726bddSHONG Yifan            doc = "A list of labels to add to a crate's `rust_library::deps` attribute.",
479*d4726bddSHONG Yifan        ),
480*d4726bddSHONG Yifan        extra_aliased_targets = attr.string_dict(
481*d4726bddSHONG Yifan            doc = "A list of targets to add to the generated aliases in the root crate_universe repository.",
482*d4726bddSHONG Yifan        ),
483*d4726bddSHONG Yifan        gen_binaries = attr.string_list(
484*d4726bddSHONG Yifan            doc = "As a list, the subset of the crate's bins that should get `rust_binary` targets produced.",
485*d4726bddSHONG Yifan        ),
486*d4726bddSHONG Yifan        gen_all_binaries = attr.bool(
487*d4726bddSHONG Yifan            doc = "If true, generates `rust_binary` targets for all of the crates bins",
488*d4726bddSHONG Yifan        ),
489*d4726bddSHONG Yifan        disable_pipelining = attr.bool(
490*d4726bddSHONG Yifan            doc = "If True, disables pipelining for library targets for this crate.",
491*d4726bddSHONG Yifan        ),
492*d4726bddSHONG Yifan        gen_build_script = attr.string(
493*d4726bddSHONG Yifan            doc = "An authorative flag to determine whether or not to produce `cargo_build_script` targets for the current crate. Supported values are 'on', 'off', and 'auto'.",
494*d4726bddSHONG Yifan            values = _OPT_BOOL_VALUES.keys(),
495*d4726bddSHONG Yifan            default = "auto",
496*d4726bddSHONG Yifan        ),
497*d4726bddSHONG Yifan        patch_args = attr.string_list(
498*d4726bddSHONG Yifan            doc = "The `patch_args` attribute of a Bazel repository rule. See [http_archive.patch_args](https://docs.bazel.build/versions/main/repo/http.html#http_archive-patch_args)",
499*d4726bddSHONG Yifan        ),
500*d4726bddSHONG Yifan        patch_tool = attr.string(
501*d4726bddSHONG Yifan            doc = "The `patch_tool` attribute of a Bazel repository rule. See [http_archive.patch_tool](https://docs.bazel.build/versions/main/repo/http.html#http_archive-patch_tool)",
502*d4726bddSHONG Yifan        ),
503*d4726bddSHONG Yifan        patches = attr.label_list(
504*d4726bddSHONG Yifan            doc = "The `patches` attribute of a Bazel repository rule. See [http_archive.patches](https://docs.bazel.build/versions/main/repo/http.html#http_archive-patches)",
505*d4726bddSHONG Yifan        ),
506*d4726bddSHONG Yifan        proc_macro_deps = _relative_label_list(
507*d4726bddSHONG Yifan            doc = "A list of labels to add to a crate's `rust_library::proc_macro_deps` attribute.",
508*d4726bddSHONG Yifan        ),
509*d4726bddSHONG Yifan        rustc_env = attr.string_dict(
510*d4726bddSHONG Yifan            doc = "Additional variables to set on a crate's `rust_library::rustc_env` attribute.",
511*d4726bddSHONG Yifan        ),
512*d4726bddSHONG Yifan        rustc_env_files = _relative_label_list(
513*d4726bddSHONG Yifan            doc = "A list of labels to set on a crate's `rust_library::rustc_env_files` attribute.",
514*d4726bddSHONG Yifan        ),
515*d4726bddSHONG Yifan        rustc_flags = attr.string_list(
516*d4726bddSHONG Yifan            doc = "A list of strings to set on a crate's `rust_library::rustc_flags` attribute.",
517*d4726bddSHONG Yifan        ),
518*d4726bddSHONG Yifan        shallow_since = attr.string(
519*d4726bddSHONG Yifan            doc = "An optional timestamp used for crates originating from a git repository instead of a crate registry. This flag optimizes fetching the source code.",
520*d4726bddSHONG Yifan        ),
521*d4726bddSHONG Yifan        override_target_lib = attr.label(
522*d4726bddSHONG Yifan            doc = "An optional alternate taget to use when something depends on this crate to allow the parent repo to provide its own version of this dependency.",
523*d4726bddSHONG Yifan        ),
524*d4726bddSHONG Yifan        override_target_proc_macro = attr.label(
525*d4726bddSHONG Yifan            doc = "An optional alternate taget to use when something depends on this crate to allow the parent repo to provide its own version of this dependency.",
526*d4726bddSHONG Yifan        ),
527*d4726bddSHONG Yifan        override_target_build_script = attr.label(
528*d4726bddSHONG Yifan            doc = "An optional alternate taget to use when something depends on this crate to allow the parent repo to provide its own version of this dependency.",
529*d4726bddSHONG Yifan        ),
530*d4726bddSHONG Yifan        override_target_bin = attr.label(
531*d4726bddSHONG Yifan            doc = "An optional alternate taget to use when something depends on this crate to allow the parent repo to provide its own version of this dependency.",
532*d4726bddSHONG Yifan        ),
533*d4726bddSHONG Yifan    ),
534*d4726bddSHONG Yifan)
535*d4726bddSHONG Yifan
536*d4726bddSHONG Yifan_from_specs = tag_class(
537*d4726bddSHONG Yifan    doc = "Generates a repo @crates from the defined `spec` tags",
538*d4726bddSHONG Yifan    attrs = dict(
539*d4726bddSHONG Yifan        name = attr.string(doc = "The name of the repo to generate", default = "crates"),
540*d4726bddSHONG Yifan        cargo_config = CRATES_VENDOR_ATTRS["cargo_config"],
541*d4726bddSHONG Yifan        generate_binaries = CRATES_VENDOR_ATTRS["generate_binaries"],
542*d4726bddSHONG Yifan        generate_build_scripts = CRATES_VENDOR_ATTRS["generate_build_scripts"],
543*d4726bddSHONG Yifan        supported_platform_triples = CRATES_VENDOR_ATTRS["supported_platform_triples"],
544*d4726bddSHONG Yifan    ),
545*d4726bddSHONG Yifan)
546*d4726bddSHONG Yifan
547*d4726bddSHONG Yifan# This should be kept in sync with crate_universe/private/crate.bzl.
548*d4726bddSHONG Yifan_spec = tag_class(
549*d4726bddSHONG Yifan    attrs = dict(
550*d4726bddSHONG Yifan        package = attr.string(
551*d4726bddSHONG Yifan            doc = "The explicit name of the package.",
552*d4726bddSHONG Yifan            mandatory = True,
553*d4726bddSHONG Yifan        ),
554*d4726bddSHONG Yifan        version = attr.string(
555*d4726bddSHONG Yifan            doc = "The exact version of the crate. Cannot be used with `git`.",
556*d4726bddSHONG Yifan        ),
557*d4726bddSHONG Yifan        artifact = attr.string(
558*d4726bddSHONG Yifan            doc = "Set to 'bin' to pull in a binary crate as an artifact dependency. Requires a nightly Cargo.",
559*d4726bddSHONG Yifan        ),
560*d4726bddSHONG Yifan        lib = attr.bool(
561*d4726bddSHONG Yifan            doc = "If using `artifact = 'bin'`, additionally setting `lib = True` declares a dependency on both the package's library and binary, as opposed to just the binary.",
562*d4726bddSHONG Yifan        ),
563*d4726bddSHONG Yifan        default_features = attr.bool(
564*d4726bddSHONG Yifan            doc = "Maps to the `default-features` flag.",
565*d4726bddSHONG Yifan            default = True,
566*d4726bddSHONG Yifan        ),
567*d4726bddSHONG Yifan        features = attr.string_list(
568*d4726bddSHONG Yifan            doc = "A list of features to use for the crate.",
569*d4726bddSHONG Yifan        ),
570*d4726bddSHONG Yifan        git = attr.string(
571*d4726bddSHONG Yifan            doc = "The Git url to use for the crate. Cannot be used with `version`.",
572*d4726bddSHONG Yifan        ),
573*d4726bddSHONG Yifan        branch = attr.string(
574*d4726bddSHONG Yifan            doc = "The git branch of the remote crate. Tied with the `git` param. Only one of branch, tag or rev may be specified. Specifying `rev` is recommended for fully-reproducible builds.",
575*d4726bddSHONG Yifan        ),
576*d4726bddSHONG Yifan        tag = attr.string(
577*d4726bddSHONG Yifan            doc = "The git tag of the remote crate. Tied with the `git` param. Only one of branch, tag or rev may be specified. Specifying `rev` is recommended for fully-reproducible builds.",
578*d4726bddSHONG Yifan        ),
579*d4726bddSHONG Yifan        rev = attr.string(
580*d4726bddSHONG Yifan            doc = "The git revision of the remote crate. Tied with the `git` param. Only one of branch, tag or rev may be specified.",
581*d4726bddSHONG Yifan        ),
582*d4726bddSHONG Yifan    ),
583*d4726bddSHONG Yifan)
584*d4726bddSHONG Yifan
585*d4726bddSHONG Yifancrate = module_extension(
586*d4726bddSHONG Yifan    implementation = _crate_impl,
587*d4726bddSHONG Yifan    tag_classes = dict(
588*d4726bddSHONG Yifan        from_cargo = _from_cargo,
589*d4726bddSHONG Yifan        annotation = _annotation,
590*d4726bddSHONG Yifan        from_specs = _from_specs,
591*d4726bddSHONG Yifan        spec = _spec,
592*d4726bddSHONG Yifan    ),
593*d4726bddSHONG Yifan)
594