@@ -858,6 +858,109 @@ def _get_host_cargo_rustc(module_ctx, host_triple, host_tools_repo):
858858
859859 return cargo_path , rustc_path
860860
861+ def _update_annotations (annotations , crate , version , triples , data , env ):
862+ """Insert build-script data/env for each target triple if keys exist."""
863+ crate_info = annotations .get (crate )
864+ if not crate_info :
865+ fail ("Crate {} not found in annotations" .format (crate ))
866+
867+ version_info = crate_info .get (version )
868+ if not version_info :
869+ fail ("Version {} not found in annotations for crate {}" .format (version , crate ))
870+
871+ for triple in triples :
872+ entry = _insert_build_script_data (version_info , data , triple , crate , version )
873+ entry = _insert_build_script_env (entry , env , triple , crate , version )
874+ crate_info [version ] = entry
875+
876+ return crate_info
877+
878+ def _insert_annotation (store , crate , annotation ):
879+ """Insert `annotation` (dict) into `store[crate][version]`.
880+
881+ Raises `fail()` if that (crate, version) pair already exists.
882+ """
883+ version = annotation ["version" ]
884+ crate_map = store .setdefault (crate , {}) # create nested map if needed
885+
886+ if version in crate_map :
887+ fail ("Duplicate annotation for crate {} version {}" .format (crate , version ))
888+
889+ crate_map [version ] = annotation
890+
891+ def _insert_select (annotation , field , value , triple , crate , version ):
892+ """
893+ Add a per-triple override to annotation[field].
894+
895+ If annotation[field] is in a list or dict, we wrap that into the common field. Then
896+
897+ On later calls we just update the existing selects.
898+ """
899+ current = annotation .get (field )
900+
901+ if type (current ) != "struct" :
902+ annotation [field ] = struct (
903+ common = current ,
904+ selects = {triple : value },
905+ )
906+ else :
907+ if triple in current .selects :
908+ fail ("Duplicate annotation for crate {} version {} triple {}" .format (crate , version , triple ))
909+ current .selects .update ({triple : value })
910+
911+ return annotation
912+
913+ def _insert_build_script_data (annotation , build_script_data , triple , crate , version ):
914+ return _insert_select (
915+ annotation ,
916+ "build_script_data" ,
917+ build_script_data ,
918+ triple ,
919+ crate ,
920+ version ,
921+ )
922+
923+ def _insert_build_script_env (annotation , build_script_env , triple , crate , version ):
924+ return _insert_select (
925+ annotation ,
926+ "build_script_env" ,
927+ build_script_env ,
928+ triple ,
929+ crate ,
930+ version ,
931+ )
932+
933+ def _create_annotation (annotation_dict ):
934+ return _crate_universe_crate .annotation (** {
935+ k : v
936+ for k , v in annotation_dict .items ()
937+ # Tag classes can't take in None, but the function requires None
938+ # instead of the empty values in many cases.
939+ # https://github.com/bazelbuild/bazel/issues/20744
940+ if v != "" and v != [] and v != {}
941+ })
942+
943+ def _collapse_crate_versions (crates , create = _create_annotation ):
944+ """{crate: {version: annotation}} -> {crate: [annotation,…]}"""
945+ return {
946+ crate : [create (v ) for v in versions .values ()]
947+ for crate , versions in crates .items ()
948+ }
949+
950+ def _collapse_versions (repo_specific , module , create = _create_annotation ):
951+ """
952+ Return new (repo_specific, module) dicts with versions collapsed.
953+
954+ repo_specific: {repo: {crate: {version: annotation}}}
955+ module : {crate: {version: annotation}}
956+ """
957+ new_repo_specific = {
958+ repo : _collapse_crate_versions (crates , create )
959+ for repo , crates in repo_specific .items ()
960+ }
961+ new_module = _collapse_crate_versions (module , create )
962+ return new_repo_specific , new_module
963+
861964def _crate_impl (module_ctx ):
862965 reproducible = True
863966 host_triple = get_host_triple (module_ctx , abi = {
@@ -927,23 +1030,45 @@ def _crate_impl(module_ctx):
9271030 if replacement :
9281031 annotation_dict ["override_targets" ]["bin" ] = str (replacement )
9291032
930- annotation = _crate_universe_crate .annotation (** {
931- k : v
932- for k , v in annotation_dict .items ()
933- # Tag classes can't take in None, but the function requires None
934- # instead of the empty values in many cases.
935- # https://github.com/bazelbuild/bazel/issues/20744
936- if v != "" and v != [] and v != {}
937- })
9381033 if not repositories :
939- _get_or_insert (module_annotations , crate , []).append (annotation )
940- for repo in repositories :
941- _get_or_insert (
942- _get_or_insert (repo_specific_annotations , repo , {}),
1034+ _insert_annotation (module_annotations , crate , annotation_dict )
1035+ else :
1036+ for repo in repositories :
1037+ # each repo gets its own top-level dict:
1038+ repo_map = repo_specific_annotations .setdefault (repo , {})
1039+ _insert_annotation (repo_map , crate , annotation_dict )
1040+
1041+ for annotation_select_tag in mod .tags .annotation_select :
1042+ annotation_select_dict = structs .to_dict (annotation_select_tag )
1043+ repositories = annotation_select_dict .pop ("repositories" )
1044+ crate = annotation_select_dict .pop ("crate" )
1045+ triples = annotation_select_dict .pop ("triples" )
1046+ version = annotation_select_dict ["version" ]
1047+ build_script_data = annotation_select_dict ["build_script_data" ]
1048+ build_script_env = annotation_select_dict ["build_script_env" ]
1049+
1050+ if repositories :
1051+ for repo in repositories :
1052+ _update_annotations (
1053+ repo_specific_annotations .get (repo , {}),
1054+ crate ,
1055+ version ,
1056+ triples ,
1057+ build_script_data ,
1058+ build_script_env ,
1059+ )
1060+ else :
1061+ _update_annotations (
1062+ module_annotations ,
9431063 crate ,
944- [],
945- ).append (annotation )
1064+ version ,
1065+ triples ,
1066+ build_script_data ,
1067+ build_script_env ,
1068+ )
9461069
1070+ # Now transform the annotations into a format that the `_generate_hub_and_spokes` function expects.
1071+ repo_specific_annotations , module_annotations = _collapse_versions (repo_specific_annotations , module_annotations )
9471072 common_specs = []
9481073 repo_specific_specs = {}
9491074 for spec in mod .tags .spec :
@@ -1389,6 +1514,35 @@ can be found below where the supported keys for each template can be found in th
13891514 },
13901515)
13911516
1517+ _annotation_select = tag_class (
1518+ doc = "A constructor for a crate dependency with selectable attributes." ,
1519+ attrs = {
1520+ "crate" : attr .string (
1521+ doc = "The name of the crate the annotation is applied to" ,
1522+ mandatory = True ,
1523+ ),
1524+ "repositories" : attr .string_list (
1525+ doc = "A list of repository names specified from `crate.from_cargo(name=...)` that this annotation is applied to. Defaults to all repositories." ,
1526+ default = [],
1527+ ),
1528+ "version" : attr .string (
1529+ doc = "The versions of the crate the annotation is applied to. Defaults to all versions." ,
1530+ default = "*" ,
1531+ ),
1532+ } | {
1533+ "build_script_data" : attr .string_list (
1534+ doc = "A list of labels to add to a crate's `cargo_build_script::data` attribute." ,
1535+ ),
1536+ "build_script_env" : attr .string_dict (
1537+ doc = "Additional environment variables to set on a crate's `cargo_build_script::env` attribute." ,
1538+ ),
1539+ "triples" : attr .string_list (
1540+ doc = "A list of triples to apply the annotation to." ,
1541+ mandatory = True ,
1542+ ),
1543+ },
1544+ )
1545+
13921546crate = module_extension (
13931547 doc = """\
13941548 Crate universe module extensions.
@@ -1408,6 +1562,7 @@ Environment Variables:
14081562 implementation = _crate_impl ,
14091563 tag_classes = {
14101564 "annotation" : _annotation ,
1565+ "annotation_select" : _annotation_select ,
14111566 "from_cargo" : _from_cargo ,
14121567 "from_specs" : _from_specs ,
14131568 "render_config" : _render_config ,
0 commit comments