diff --git a/rs/BUILD.bazel b/rs/BUILD.bazel index f563df71..9e376b75 100644 --- a/rs/BUILD.bazel +++ b/rs/BUILD.bazel @@ -44,6 +44,7 @@ bzl_library( srcs = ["extensions.bzl"], visibility = ["//visibility:public"], deps = [ + "//rs/platforms:triples", "//rs/private:annotations", "//rs/private:cargo_credentials", "//rs/private:cargo_workspace_graph", diff --git a/rs/extensions.bzl b/rs/extensions.bzl index 21ea4e73..d2a68594 100644 --- a/rs/extensions.bzl +++ b/rs/extensions.bzl @@ -2,6 +2,7 @@ load("@aspect_tools_telemetry_report//:defs.bzl", "TELEMETRY") # buildifier: di load("@bazel_lib//lib:repo_utils.bzl", "repo_utils") load("@bazel_skylib//lib:paths.bzl", "paths") load("@rs_rust_host_tools//:defs.bzl", "RS_HOST_CARGO_LABEL") +load("//rs/platforms:triples.bzl", "SUPPORTED_EXEC_TRIPLES") load("//rs/private:annotations.bzl", "annotation_for", "build_annotation_map", "well_known_annotation_snippet_paths") load("//rs/private:cargo_credentials.bzl", "load_cargo_credentials") load( @@ -267,6 +268,7 @@ def _generate_hub_and_spokes( annotations = annotations, platform_triples = platform_triples, materialize_workspace_members = False, + exec_platform_triples = SUPPORTED_EXEC_TRIPLES, validate_lockfile = validate_lockfile, debug = debug, dep_label_prefix = "@%s//:" % hub_name, @@ -277,6 +279,7 @@ def _generate_hub_and_spokes( platform_cfg_attrs = workspace_resolution.platform_cfg_attrs workspace_dep_labels_by_triple = workspace_resolution.workspace_dep_labels_by_triple workspace_dep_versions_by_name = workspace_resolution.workspace_dep_versions_by_name + exec_feature_resolutions_by_fq_crate = workspace_resolution.exec_feature_resolutions_by_fq_crate _date(mctx, "set up initial deps!") @@ -290,6 +293,7 @@ def _generate_hub_and_spokes( source = package["source"] feature_resolutions = feature_resolutions_by_fq_crate[_fq_crate(crate_name, version)] + exec_feature_resolutions = exec_feature_resolutions_by_fq_crate[_fq_crate(crate_name, version)] annotation = annotation_for(annotations, crate_name, version, hub_name) suggested_annotation = None @@ -323,6 +327,7 @@ crate.annotation( gen_build_script = annotation.gen_build_script, build_script_deps = [], build_script_deps_select = _select(feature_resolutions.build_deps), + exec_build_script_deps_select = _select(exec_feature_resolutions.build_deps), build_script_data = annotation.build_script_data, build_script_data_select = annotation.build_script_data_select, build_script_env = annotation.build_script_env, @@ -338,9 +343,14 @@ crate.annotation( deps = annotation.deps, crate_tags = annotation.tags, deps_select = _select(feature_resolutions.deps), + exec_deps_select = _select(exec_feature_resolutions.deps), aliases = feature_resolutions.aliases, + exec_aliases = exec_feature_resolutions.aliases, crate_features = annotation.crate_features, crate_features_select = _select(feature_resolutions.features_enabled), + exec_crate_features_select = _select(exec_feature_resolutions.features_enabled), + target_active = bool(feature_resolutions.active), + exec_active = bool(exec_feature_resolutions.active), use_legacy_rules_rust_platforms = use_legacy_rules_rust_platforms, ) diff --git a/rs/private/cargo_workspace_graph.bzl b/rs/private/cargo_workspace_graph.bzl index 9baee0e8..18f93a81 100644 --- a/rs/private/cargo_workspace_graph.bzl +++ b/rs/private/cargo_workspace_graph.bzl @@ -1,6 +1,6 @@ load("@bazel_skylib//lib:paths.bzl", "paths") load("//rs/private:cfg_parser.bzl", "cfg_matches_expr_for_cfg_attrs", "triple_to_cfg_attrs") -load("//rs/private:resolver.bzl", "resolve") +load("//rs/private:resolver.bzl", "resolve", "seed_exec_build_dependencies") load("//rs/private:select_utils.bzl", "compute_select") load("//rs/private:semver.bzl", "select_matching_version") @@ -64,6 +64,7 @@ def cfg_match_info_for_target(target, platform_cfg_attrs, cfg_match_cache): def new_feature_resolutions(package_index, possible_deps, possible_features, platform_triples): return struct( + active = set(), features_enabled = {triple: set() for triple in platform_triples}, build_deps = {triple: set() for triple in platform_triples}, deps = {triple: set() for triple in platform_triples}, @@ -472,6 +473,11 @@ def _resolve_possible_deps( else: dep["target"] = set(match_info.matches) +def _apply_annotation_features(feature_resolutions, annotation): + for triple, features in feature_resolutions.features_enabled.items(): + features.update(annotation.crate_features) + features.update(annotation.crate_features_select.get(triple, [])) + def resolve_cargo_workspace_members( ctx, *, @@ -483,6 +489,7 @@ def resolve_cargo_workspace_members( annotations, platform_triples, materialize_workspace_members, + exec_platform_triples = [], validate_lockfile = True, debug = False, dep_label_prefix = "//:", @@ -490,12 +497,12 @@ def resolve_cargo_workspace_members( watch_manifests = False, use_legacy_rules_rust_platforms = False): platform_cfg_attrs = [triple_to_cfg_attrs(triple) for triple in platform_triples] - platform_cfg_attrs_by_triple = {} - for cfg_attr in platform_cfg_attrs: - platform_cfg_attrs_by_triple[cfg_attr["_triple"]] = cfg_attr + platform_cfg_attrs_by_triple = {cfg_attr["_triple"]: cfg_attr for cfg_attr in platform_cfg_attrs} cfg_match_cache = {None: struct(matches = platform_triples, uses_feature_cfg = False)} + exec_platform_cfg_attrs_by_triple = {triple: triple_to_cfg_attrs(triple) for triple in exec_platform_triples} + workspace_member_keys = {} for package in cargo_metadata["packages"]: workspace_member_keys[(package["name"], package["version"])] = True @@ -535,6 +542,21 @@ def resolve_cargo_workspace_members( resolver_packages.append(resolver_package) + exec_feature_resolutions_by_fq_crate = {} + exec_resolver_packages = [] + if exec_platform_triples: + for package in resolver_packages: + target_resolution = package["feature_resolutions"] + target_resolution.build_deps.update({triple: set() for triple in exec_platform_triples}) + exec_resolution = new_feature_resolutions( + target_resolution.package_index, + [dict(dep) for dep in target_resolution.possible_deps], + target_resolution.possible_features, + exec_platform_triples, + ) + exec_feature_resolutions_by_fq_crate[fq_crate(package["name"], package["version"])] = exec_resolution + exec_resolver_packages.append(dict(package, feature_resolutions = exec_resolution)) + _resolve_possible_deps( resolver_packages, resolver_versions_by_name, @@ -545,6 +567,17 @@ def resolve_cargo_workspace_members( dep_label_prefix, ) + if exec_platform_triples: + _resolve_possible_deps( + exec_resolver_packages, + resolver_versions_by_name, + exec_feature_resolutions_by_fq_crate, + exec_platform_triples, + exec_platform_cfg_attrs_by_triple.values(), + {None: struct(matches = exec_platform_triples, uses_feature_cfg = False)}, + dep_label_prefix, + ) + workspace_fq_deps = compute_workspace_fq_deps(workspace_members, resolver_versions_by_name) workspace_dep_versions_by_name = {} workspace_dep_labels_by_triple = {triple: set() for triple in platform_triples} @@ -554,6 +587,7 @@ def resolve_cargo_workspace_members( ctx.watch(package["manifest_path"]) package_feature_resolutions = feature_resolutions_by_fq_crate[fq_crate(package["name"], package["version"])] + package_feature_resolutions.active.update(platform_triples) if "default" in package.get("features", {}): for triple in platform_triples: package_feature_resolutions.features_enabled[triple].add("default") @@ -611,8 +645,11 @@ def resolve_cargo_workspace_members( match_info = cfg_match_info_for_target(target, platform_cfg_attrs, cfg_match_cache) for triple in match_info.matches: + if dep.get("kind", "normal") == "build": + continue if not is_first_party_dep or materialize_workspace_members: workspace_dep_labels_by_triple[triple].add(":" + dep_name) + feature_resolutions.active.add(triple) feature_resolutions.features_enabled[triple].update(features) for crate, annotation_versions in annotations.items(): @@ -625,15 +662,17 @@ def resolve_cargo_workspace_members( if not annotation.crate_features and not annotation.crate_features_select: continue for version in target_versions: - features_enabled = feature_resolutions_by_fq_crate[fq_crate(crate, version)].features_enabled - if annotation.crate_features: - for triple in platform_triples: - features_enabled[triple].update(annotation.crate_features) - for triple, features in annotation.crate_features_select.items(): - if triple in features_enabled: - features_enabled[triple].update(features) + fq = fq_crate(crate, version) + _apply_annotation_features(feature_resolutions_by_fq_crate[fq], annotation) + + if exec_platform_triples: + _apply_annotation_features(exec_feature_resolutions_by_fq_crate[fq], annotation) + + resolve(ctx, resolver_packages, feature_resolutions_by_fq_crate, platform_cfg_attrs_by_triple, debug, include_build_dependencies = not exec_platform_triples) - resolve(ctx, resolver_packages, feature_resolutions_by_fq_crate, platform_cfg_attrs_by_triple, debug) + if exec_platform_triples: + seed_exec_build_dependencies(resolver_packages, exec_resolver_packages, exec_platform_cfg_attrs_by_triple) + resolve(ctx, exec_resolver_packages, exec_feature_resolutions_by_fq_crate, exec_platform_cfg_attrs_by_triple, debug) for package in packages: feature_resolutions = package["feature_resolutions"] @@ -651,6 +690,7 @@ def resolve_cargo_workspace_members( return struct( cfg_match_cache = cfg_match_cache, + exec_feature_resolutions_by_fq_crate = exec_feature_resolutions_by_fq_crate, feature_resolutions_by_fq_crate = feature_resolutions_by_fq_crate, platform_cfg_attrs = platform_cfg_attrs, platform_cfg_attrs_by_triple = platform_cfg_attrs_by_triple, diff --git a/rs/private/repository_utils.bzl b/rs/private/repository_utils.bzl index ae205629..676ea9fc 100644 --- a/rs/private/repository_utils.bzl +++ b/rs/private/repository_utils.bzl @@ -165,8 +165,8 @@ def cargo_build_file_values(rctx, cargo_toml, gen_binaries, package_path = "", g ) _RUST_CRATE_MACRO_CALL = """{indent}rust_crate( -{indent} name = {name}, -{indent} crate_name = {crate_name}, +{indent} name = {name}{name_suffix}, +{indent} crate_name = {crate_name} or {name}.replace("-", "_"), {indent} purl = {purl}, {indent} version = {version}, {indent} aliases = {{ @@ -185,7 +185,7 @@ _RUST_CRATE_MACRO_CALL = """{indent}rust_crate( {indent} edition = {edition}, {rustc_env_attr}{indent} rustc_flags = {rustc_flags}{conditional_rustc_flags}, {indent} tags = {tags}, -{indent} target_compatible_with = RESOLVED_PLATFORMS, +{indent} target_compatible_with = {target_compatible_with}, {indent} links = {links}, {indent} build_script = {build_script}, {indent} build_script_data = {build_script_data}{conditional_build_script_data}, @@ -201,22 +201,39 @@ _RUST_CRATE_MACRO_CALL = """{indent}rust_crate( {indent} has_lib = {has_lib}, {indent} binaries = {binaries}, {indent} use_legacy_rules_rust_platforms = {use_legacy_rules_rust_platforms}, +{indent} name_suffix = {crate_name_suffix}, {skip_deps_verification_attr}{indent}) """ -def render_rust_crate_call(attr, values, bazel_metadata = {}, extra_deps = "", indent = "", skip_deps_verification = False): +def _render_rust_crate_call( + attr, + values, + aliases, + crate_features_select, + deps_select, + build_deps_select, + name_suffix, + binaries, + bazel_metadata, + extra_deps, + indent, + skip_deps_verification): + target_compatible_with = "RESOLVED_PLATFORMS" + if name_suffix == "_exec" or not getattr(attr, "target_active", True) or crate_features_select.keys() != attr.crate_features_select.keys(): + target_compatible_with = "None" + # We keep conditional_crate_features unrendered here because it must be treated specially for build scripts. # See `rust_crate.bzl` for details. crate_features, conditional_crate_features = compute_select( _exclude_deps_from_features(attr.crate_features), - {platform: _exclude_deps_from_features(features) for platform, features in attr.crate_features_select.items()}, + {platform: _exclude_deps_from_features(features) for platform, features in crate_features_select.items()}, ) use_legacy_rules_rust_platforms = attr.use_legacy_rules_rust_platforms - build_deps, conditional_build_deps = render_select(attr.build_script_deps, attr.build_script_deps_select, use_legacy_rules_rust_platforms) + build_deps, conditional_build_deps = render_select(attr.build_script_deps, build_deps_select, use_legacy_rules_rust_platforms) build_script_data, conditional_build_script_data = render_select(attr.build_script_data, attr.build_script_data_select, use_legacy_rules_rust_platforms) build_script_tools, conditional_build_script_tools = render_select(attr.build_script_tools, attr.build_script_tools_select, use_legacy_rules_rust_platforms) rustc_flags, conditional_rustc_flags = render_select(attr.rustc_flags, attr.rustc_flags_select, use_legacy_rules_rust_platforms) - deps, conditional_deps = render_select(attr.deps + bazel_metadata.get("deps", []), attr.deps_select, use_legacy_rules_rust_platforms) + deps, conditional_deps = render_select(attr.deps + bazel_metadata.get("deps", []), deps_select, use_legacy_rules_rust_platforms) conditional_build_script_env = render_select_build_script_env(attr.build_script_env_select, use_legacy_rules_rust_platforms) @@ -239,17 +256,18 @@ def render_rust_crate_call(attr, values, bazel_metadata = {}, extra_deps = "", i return _RUST_CRATE_MACRO_CALL.format( indent = indent, name = values["name"], + name_suffix = " + " + repr(name_suffix) if name_suffix else "", crate_name = values["crate_name"], purl = values["purl"], version = values["version"], - aliases = list_indent.join(['"%s": "%s"' % kv for kv in attr.aliases.items()]), + aliases = list_indent.join(['"%s": "%s"' % kv for kv in aliases.items()]), deps = list_indent.join(['"%s"' % d for d in sorted(deps)]), extra_deps = extra_deps, conditional_deps = " + " + conditional_deps if conditional_deps else "", data = list_indent.join(['"%s"' % str(d) for d in attr.data]), extra_compile_data_attr = extra_compile_data_attr, crate_features = repr(sorted(crate_features)), - triples = repr(attr.crate_features_select.keys()), + triples = repr(crate_features_select.keys()), conditional_crate_features = repr(conditional_crate_features), crate_root = values["crate_root"], edition = values["edition"], @@ -257,6 +275,7 @@ def render_rust_crate_call(attr, values, bazel_metadata = {}, extra_deps = "", i rustc_flags = repr(rustc_flags), conditional_rustc_flags = " + " + conditional_rustc_flags if conditional_rustc_flags else "", tags = repr(attr.crate_tags), + target_compatible_with = target_compatible_with, links = values["links"], build_script = values["build_script"], build_script_data = repr(build_script_data), @@ -272,11 +291,88 @@ def render_rust_crate_call(attr, values, bazel_metadata = {}, extra_deps = "", i build_script_tags = repr(attr.build_script_tags), is_proc_macro = values["is_proc_macro"], has_lib = values["has_lib"], - binaries = values["binaries"], + binaries = binaries, use_legacy_rules_rust_platforms = use_legacy_rules_rust_platforms, + crate_name_suffix = repr(name_suffix), skip_deps_verification_attr = skip_deps_verification_attr, ) +def _merge_target_and_exec(target_values, exec_values): + for key, exec_value in exec_values.items(): + if key in target_values and target_values[key] != exec_value: + return None + return target_values | exec_values + +def render_rust_crate_call(attr, values, bazel_metadata = {}, extra_deps = "", indent = "", skip_deps_verification = False): + exec_active = getattr(attr, "exec_active", False) + aliases = attr.aliases + crate_features_select = attr.crate_features_select + deps_select = attr.deps_select + build_deps_select = attr.build_script_deps_select + binaries = values["binaries"] + + if not getattr(attr, "target_active", True) and exec_active: + aliases = attr.exec_aliases + crate_features_select = attr.exec_crate_features_select + deps_select = attr.exec_deps_select + build_deps_select = attr.exec_build_script_deps_select + binaries = "{}" + elif exec_active: + merged_crate_features_select = _merge_target_and_exec(crate_features_select, attr.exec_crate_features_select) + merged_deps_select = _merge_target_and_exec(deps_select, attr.exec_deps_select) + merged_build_deps_select = _merge_target_and_exec(build_deps_select, attr.exec_build_script_deps_select) + merged_aliases = _merge_target_and_exec(aliases, attr.exec_aliases) + if None in [merged_crate_features_select, merged_deps_select, merged_build_deps_select, merged_aliases]: + target_call = _render_rust_crate_call( + attr = attr, + values = values, + aliases = aliases, + crate_features_select = crate_features_select, + deps_select = deps_select, + build_deps_select = build_deps_select, + name_suffix = "_target", + binaries = binaries, + bazel_metadata = bazel_metadata, + extra_deps = extra_deps, + indent = indent, + skip_deps_verification = skip_deps_verification, + ) + exec_call = _render_rust_crate_call( + attr = attr, + values = values, + aliases = attr.exec_aliases, + crate_features_select = attr.exec_crate_features_select, + deps_select = attr.exec_deps_select, + build_deps_select = attr.exec_build_script_deps_select, + name_suffix = "_exec", + binaries = "{}", + bazel_metadata = bazel_metadata, + extra_deps = extra_deps, + indent = indent, + skip_deps_verification = skip_deps_verification, + ) + return target_call + "\n" + exec_call + + aliases = merged_aliases + crate_features_select = merged_crate_features_select + deps_select = merged_deps_select + build_deps_select = merged_build_deps_select + + return _render_rust_crate_call( + attr = attr, + values = values, + aliases = aliases, + crate_features_select = crate_features_select, + deps_select = deps_select, + build_deps_select = build_deps_select, + name_suffix = "", + binaries = binaries, + bazel_metadata = bazel_metadata, + extra_deps = extra_deps, + indent = indent, + skip_deps_verification = skip_deps_verification, + ) + def render_build_file_content(rctx, attr, values, bazel_metadata = {}): additive_build_file_content = "" if attr.additive_build_file: @@ -299,6 +395,7 @@ rust_crate_attrs = { "gen_build_script": attr.string(), "build_script_deps": attr.label_list(), "build_script_deps_select": _label_list_dict(), + "exec_build_script_deps_select": _label_list_dict(), "build_script_data": attr.label_list(), "build_script_data_select": _label_list_dict(), "build_script_env": attr.string_dict(), @@ -314,9 +411,14 @@ rust_crate_attrs = { "data": attr.label_list(), "deps": attr.label_list(), "deps_select": _label_list_dict(), + "exec_deps_select": _label_list_dict(), "aliases": attr.string_dict(), + "exec_aliases": attr.string_dict(), "crate_features": attr.string_list(), "crate_features_select": attr.string_list_dict(), + "exec_crate_features_select": attr.string_list_dict(), + "target_active": attr.bool(), + "exec_active": attr.bool(), "use_legacy_rules_rust_platforms": attr.bool(), } diff --git a/rs/private/resolver.bzl b/rs/private/resolver.bzl index dc122de0..a984d545 100644 --- a/rs/private/resolver.bzl +++ b/rs/private/resolver.bzl @@ -16,8 +16,7 @@ def _count(feature_resolutions_by_fq_crate): return n def _dep_target_matches_triple(dep, triple, package_feature_set, cfg_attrs_by_triple): - remaining = dep["target"] - if triple not in remaining: + if triple not in dep["target"]: return False if not dep.get("feature_sensitive", False): @@ -30,7 +29,7 @@ def _dep_target_matches_triple(dep, triple, package_feature_set, cfg_attrs_by_tr features = package_feature_set, ).matches) -def _resolve_one_round(packages, dirty_package_indices, cfg_attrs_by_triple, debug): +def _resolve_one_round(packages, dirty_package_indices, cfg_attrs_by_triple, debug, include_build_dependencies): new_dirty_package_indices = set() for index in dirty_package_indices: @@ -43,13 +42,13 @@ def _resolve_one_round(packages, dirty_package_indices, cfg_attrs_by_triple, deb deps = feature_resolutions.deps if _propagate_feature_enablement( - package_changed, new_dirty_package_indices, package, features_enabled, feature_resolutions, cfg_attrs_by_triple, debug, + include_build_dependencies, ): package_changed = True @@ -60,6 +59,8 @@ def _resolve_one_round(packages, dirty_package_indices, cfg_attrs_by_triple, deb continue kind = dep.get("kind", "normal") + if kind == "build" and not include_build_dependencies: + continue dep_feature_resolutions = dep["feature_resolutions"] @@ -77,15 +78,17 @@ def _resolve_one_round(packages, dirty_package_indices, cfg_attrs_by_triple, deb else: match = dep["target"] - to_remove = None for triple in match: + if triple not in feature_resolutions.active: + continue + if optional: features_for_triple = features_enabled[triple] if dep_name not in features_for_triple and prefixed_dep_alias not in features_for_triple: continue triple_deps = deps[triple] if kind == "normal" else feature_resolutions.build_deps[triple] - if package_changed or bazel_target not in triple_deps: + if bazel_target not in triple_deps: package_changed = True triple_deps.add(bazel_target) @@ -94,21 +97,16 @@ def _resolve_one_round(packages, dirty_package_indices, cfg_attrs_by_triple, deb triple_features = dep_feature_resolutions.features_enabled[triple] + if triple not in dep_feature_resolutions.active: + dep_feature_resolutions.active.add(triple) + new_dirty_package_indices.add(dep_feature_resolutions.package_index) + dep_features = dep.get("features") if dep_features: prev_length = len(triple_features) triple_features.update(dep_features) if prev_length != len(triple_features): new_dirty_package_indices.add(dep_feature_resolutions.package_index) - if not to_remove: - to_remove = set() - to_remove.add(triple) - - if to_remove: - if len(to_remove) == len(match): - dep["bazel_target"] = None - else: - match.difference_update(to_remove) if package_changed: new_dirty_package_indices.add(index) @@ -116,17 +114,18 @@ def _resolve_one_round(packages, dirty_package_indices, cfg_attrs_by_triple, deb return new_dirty_package_indices def _propagate_feature_enablement( - package_changed, dirty_package_indices, package, features_enabled, feature_resolutions, cfg_attrs_by_triple, - debug): + debug, + include_build_dependencies): + package_changed = False possible_features = feature_resolutions.possible_features for triple, feature_set in features_enabled.items(): - if not feature_set: + if triple not in feature_resolutions.active or not feature_set: continue # Enable any features that are implied by previously-enabled features. @@ -146,7 +145,7 @@ def _propagate_feature_enablement( dep_name = feature[:idx] dep_feature = feature[idx + 1:] - dep_optional = False + has_optional_dependency = False optional_marker = False if dep_name[-1] == "?": optional_marker = True @@ -154,19 +153,30 @@ def _propagate_feature_enablement( found = False for dep in feature_resolutions.possible_deps: - if dep_name == dep["name"] and _dep_target_matches_triple(dep, triple, feature_set, cfg_attrs_by_triple): - found = True - dep_optional = dep.get("optional", False) - if not optional_marker or not dep_optional or dep_name in feature_set or ("dep:" + dep_name) in feature_set: - dep_feature_resolutions = dep["feature_resolutions"] - triple_features = dep_feature_resolutions.features_enabled[triple] - if dep_feature not in triple_features: - triple_features.add(dep_feature) - dirty_package_indices.add(dep_feature_resolutions.package_index) - break + if dep_name != dep["name"]: + continue + + defer_build_dependency = dep.get("kind", "normal") == "build" and not include_build_dependencies + if not defer_build_dependency and not _dep_target_matches_triple(dep, triple, feature_set, cfg_attrs_by_triple): + continue + + found = True + dep_optional = dep.get("optional", False) + has_optional_dependency = has_optional_dependency or dep_optional + if optional_marker and dep_optional and dep_name not in feature_set and ("dep:" + dep_name) not in feature_set: + continue + + if defer_build_dependency: + dep.setdefault("deferred_features", set()).add(dep_feature) + else: + dep_feature_resolutions = dep["feature_resolutions"] + triple_features = dep_feature_resolutions.features_enabled[triple] + if dep_feature not in triple_features: + triple_features.add(dep_feature) + dirty_package_indices.add(dep_feature_resolutions.package_index) # Only optional deps need to be explicitly enabled when a subfeature is toggled. - if dep_optional and (not optional_marker) and dep_name not in feature_set: + if has_optional_dependency and (not optional_marker) and dep_name not in feature_set: package_changed = True feature_set.add(dep_name) @@ -177,19 +187,53 @@ def _propagate_feature_enablement( _MAX_ROUNDS = 50 -def resolve(mctx, packages, feature_resolutions_by_fq_crate, cfg_attrs_by_triple, debug): +def resolve(mctx, packages, feature_resolutions_by_fq_crate, cfg_attrs_by_triple, debug, include_build_dependencies = True): # Do some rounds of mutual resolution; bail when no more changes dirty_package_indices = range(len(packages)) for i in range(_MAX_ROUNDS): mctx.report_progress("Running round %s of dependency/feature resolution" % i) - dirty_package_indices = _resolve_one_round(packages, dirty_package_indices, cfg_attrs_by_triple, debug) + dirty_package_indices = _resolve_one_round(packages, dirty_package_indices, cfg_attrs_by_triple, debug, include_build_dependencies) if not dirty_package_indices: if debug: count = _count(feature_resolutions_by_fq_crate) print("Got count", count, "in", i + 1, "rounds") - break + return dirty_package_indices = sorted(dirty_package_indices) - if i == _MAX_ROUNDS: - fail("Resolution did not converge! This is likely a bug in rules_rs, please report it to github.com/hermeticbuild/rules_rs") + fail("Resolution did not converge! This is likely a bug in rules_rs, please report it to github.com/hermeticbuild/rules_rs") + +def seed_exec_build_dependencies(packages, exec_packages, exec_cfg_attrs_by_triple): + """Seeds exec resolution from build dependencies of target-active packages.""" + for package, exec_package in zip(packages, exec_packages): + target_resolution = package["feature_resolutions"] + exec_resolution = exec_package["feature_resolutions"] + + if not target_resolution.active: + continue + + target_features = set() + for triple in target_resolution.active: + target_features.update(target_resolution.features_enabled[triple]) + + for target_dep, dep in zip(target_resolution.possible_deps, exec_resolution.possible_deps): + bazel_target = dep.get("bazel_target") + if dep.get("kind", "normal") != "build" or not bazel_target: + continue + + dep_name = dep["name"] + if dep.get("optional", False) and dep_name not in target_features and ("dep:" + dep_name) not in target_features: + continue + + dep_resolution = dep["feature_resolutions"] + for exec_triple in dep["target"]: + if not _dep_target_matches_triple(dep, exec_triple, target_features, exec_cfg_attrs_by_triple): + continue + + target_resolution.build_deps[exec_triple].add(bazel_target) + if "package" in dep: + target_resolution.aliases[bazel_target] = dep_name.replace("-", "_") + + dep_resolution.active.add(exec_triple) + dep_resolution.features_enabled[exec_triple].update(dep.get("features", [])) + dep_resolution.features_enabled[exec_triple].update(target_dep.get("deferred_features", [])) diff --git a/rs/rules_rust.bzl b/rs/rules_rust.bzl index 7caba377..b4f4596a 100644 --- a/rs/rules_rust.bzl +++ b/rs/rules_rust.bzl @@ -31,9 +31,9 @@ def _rules_rust_impl(mctx): http_archive( name = "rules_rust", - integrity = "sha256-gf19wNwRaWAqlUwFMLUfQq51YKGtSc7zU5TpXxGYfhc=", - strip_prefix = "rules_rust-0a4149d8a1af5f5881e73f7e2d5ff490b633fea4", - url = "https://github.com/hermeticbuild/rules_rust/archive/0a4149d8a1af5f5881e73f7e2d5ff490b633fea4.tar.gz", + integrity = "sha256-MyZ49sySXriUUCZjcHpHHvMtHBVs35IfSfBIOzp5i1I=", + strip_prefix = "rules_rust-7837073014b566291368027aa116cd03fe108049", + url = "https://github.com/hermeticbuild/rules_rust/archive/7837073014b566291368027aa116cd03fe108049.tar.gz", patches = patches, patch_strip = strip, ) diff --git a/rs/rust_crate.bzl b/rs/rust_crate.bzl index 8af42ef0..d9e39a3e 100644 --- a/rs/rust_crate.bzl +++ b/rs/rust_crate.bzl @@ -45,13 +45,33 @@ def rust_crate( use_legacy_rules_rust_platforms, extra_compile_data = [], rustc_env = {}, - skip_deps_verification = False): + skip_deps_verification = False, + name_suffix = ""): + build_script_name = "_bs" + name_suffix + if target_compatible_with == None: + target_compatible_with = select({ + _platform(triple, use_legacy_rules_rust_platforms): [] + for triple in triples + } | {"//conditions:default": ["@platforms//:incompatible"]}) + package_metadata( name = name + "_package_metadata", purl = purl, visibility = ["//visibility:public"], ) + if name_suffix == "_exec": + target_exec_alias_name = name.removesuffix(name_suffix) + native.alias( + name = target_exec_alias_name, + actual = select({ + "@rules_rust//cargo/settings:use_exec_features_enabled": name, + "//conditions:default": target_exec_alias_name + "_target", + }), + tags = ["crate-name=" + (crate_name or target_exec_alias_name)] + tags, + visibility = ["//visibility:public"], + ) + compile_data = native.glob( include = ["**"], exclude = [ @@ -74,7 +94,7 @@ def rust_crate( ) default_tags = [ - "crate-name=" + name, + "crate-name=" + (crate_name or name), "manual", "noclippy", "norustfmt", @@ -114,29 +134,29 @@ def rust_crate( # The build script is cfg-exec, but the features must be selected according to the target. # Only stamp out one target per triple when there are per-platform feature deltas. for triple in triples: - build_script_name = "_bs_" + triple - branches[_platform(triple, use_legacy_rules_rust_platforms)] = build_script_name + triple_build_script_name = build_script_name + "_" + triple + branches[_platform(triple, use_legacy_rules_rust_platforms)] = triple_build_script_name cargo_build_script( - name = build_script_name, + name = triple_build_script_name, crate_features = crate_features + conditional_crate_features.get(triple, []), **build_script_kwargs ) native.alias( - name = "_bs", + name = build_script_name, actual = select(branches), tags = build_script_target_tags, ) else: cargo_build_script( - name = "_bs", + name = build_script_name, crate_features = crate_features, **build_script_kwargs ) - maybe_build_script = ["_bs"] + maybe_build_script = [build_script_name] else: maybe_build_script = [] diff --git a/test/MODULE.bazel b/test/MODULE.bazel index 37fcd9ec..5aea9601 100644 --- a/test/MODULE.bazel +++ b/test/MODULE.bazel @@ -152,6 +152,7 @@ TESTS = [ "build_script_env_select", "build_script_runtime_env", "build_dep_features", + "build_exec_feature_split", "cfg_feature_target_dep", "empty_workspace", "feature_name_overrides_implicit_dep", diff --git a/test/MODULE.bazel.lock b/test/MODULE.bazel.lock index 4c39a38a..fc093cfa 100644 --- a/test/MODULE.bazel.lock +++ b/test/MODULE.bazel.lock @@ -2435,6 +2435,8 @@ "paste_1.0.15": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"paste-test-suite\",\"req\":\"^0\"},{\"kind\":\"dev\",\"name\":\"rustversion\",\"req\":\"^1.0\"},{\"features\":[\"diff\"],\"kind\":\"dev\",\"name\":\"trybuild\",\"req\":\"^1.0.49\"}],\"features\":{}}", "pastey_0.1.1": "{\"dependencies\":[],\"features\":{}}", "pastey_0.2.3": "{\"dependencies\":[],\"features\":{}}", + "path+build_exec_feature_split/vendor/consumer_feature-split-consumer": "{\"dependencies\":[{\"default_features\":true,\"features\":[\"target_feature\"],\"name\":\"feature-split-shared\",\"optional\":false},{\"default_features\":true,\"features\":[\"exec_feature\"],\"kind\":\"build\",\"name\":\"feature-split-shared\",\"optional\":false}],\"features\":{},\"strip_prefix\":\"\"}", + "path+build_exec_feature_split/vendor/shared_feature-split-shared": "{\"dependencies\":[],\"features\":{\"exec_feature\":[],\"target_feature\":[]},\"strip_prefix\":\"\"}", "path+cfg_feature_target_dep/inner_inner": "{\"dependencies\":[{\"name\":\"itoa\",\"req\":\"1.0.15\",\"target\":\"cfg(feature = \\\"with_itoa\\\")\"}],\"features\":{\"with_itoa\":[]},\"strip_prefix\":\"\"}", "path+first_party_feature_propagation/../first_party_feature_propagation.deps/dep_leaf_dep_leaf": "{\"dependencies\":[{\"default_features\":true,\"features\":[],\"name\":\"itoa\",\"optional\":true,\"req\":\"1.0.15\"},{\"default_features\":true,\"features\":[],\"name\":\"ryu\",\"optional\":true,\"req\":\"1.0.20\"}],\"features\":{\"default\":[],\"default_extra\":[\"dep:itoa\"],\"with_extra\":[\"dep:ryu\"]},\"strip_prefix\":\"\"}", "path+vendored_crate_override/vendor/itoa_itoa": "{\"dependencies\":[{\"default_features\":true,\"features\":[],\"name\":\"no-panic\",\"optional\":true,\"req\":\"0.1\"}],\"features\":{},\"strip_prefix\":\"\"}", diff --git a/test/build_exec_feature_split/Cargo.lock b/test/build_exec_feature_split/Cargo.lock new file mode 100644 index 00000000..048c9ae0 --- /dev/null +++ b/test/build_exec_feature_split/Cargo.lock @@ -0,0 +1,21 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "build-exec-feature-split" +version = "0.1.0" +dependencies = [ + "feature-split-consumer", +] + +[[package]] +name = "feature-split-consumer" +version = "0.1.0" +dependencies = [ + "feature-split-shared", +] + +[[package]] +name = "feature-split-shared" +version = "0.1.0" diff --git a/test/build_exec_feature_split/Cargo.toml b/test/build_exec_feature_split/Cargo.toml new file mode 100644 index 00000000..26dfdbb0 --- /dev/null +++ b/test/build_exec_feature_split/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "build-exec-feature-split" +version = "0.1.0" + +[workspace] +resolver = "2" +exclude = ["vendor/consumer", "vendor/shared"] + +[dependencies] +feature-split-consumer = { path = "vendor/consumer" } + +[patch.crates-io] +feature-split-shared = { path = "vendor/shared" } diff --git a/test/build_exec_feature_split/src/lib.rs b/test/build_exec_feature_split/src/lib.rs new file mode 100644 index 00000000..e69de29b diff --git a/test/build_exec_feature_split/vendor/consumer/Cargo.toml b/test/build_exec_feature_split/vendor/consumer/Cargo.toml new file mode 100644 index 00000000..fb3bc49a --- /dev/null +++ b/test/build_exec_feature_split/vendor/consumer/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "feature-split-consumer" +version = "0.1.0" +edition = "2024" + +[dependencies] +# The target crate must not receive exec_feature from build-dependencies. +feature-split-shared = { path = "../shared", features = ["target_feature"] } + +[build-dependencies] +# The build script must not receive target_feature from dependencies. +feature-split-shared = { path = "../shared", features = ["exec_feature"] } diff --git a/test/build_exec_feature_split/vendor/consumer/build.rs b/test/build_exec_feature_split/vendor/consumer/build.rs new file mode 100644 index 00000000..6a8e211f --- /dev/null +++ b/test/build_exec_feature_split/vendor/consumer/build.rs @@ -0,0 +1,3 @@ +fn main() { + feature_split_shared::exec_only(); +} diff --git a/test/build_exec_feature_split/vendor/consumer/src/lib.rs b/test/build_exec_feature_split/vendor/consumer/src/lib.rs new file mode 100644 index 00000000..03b43c9b --- /dev/null +++ b/test/build_exec_feature_split/vendor/consumer/src/lib.rs @@ -0,0 +1 @@ +pub use feature_split_shared::target_only; diff --git a/test/build_exec_feature_split/vendor/shared/Cargo.toml b/test/build_exec_feature_split/vendor/shared/Cargo.toml new file mode 100644 index 00000000..6cd26e08 --- /dev/null +++ b/test/build_exec_feature_split/vendor/shared/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "feature-split-shared" +version = "0.1.0" +edition = "2024" + +[features] +exec_feature = [] +target_feature = [] diff --git a/test/build_exec_feature_split/vendor/shared/src/lib.rs b/test/build_exec_feature_split/vendor/shared/src/lib.rs new file mode 100644 index 00000000..799e33ec --- /dev/null +++ b/test/build_exec_feature_split/vendor/shared/src/lib.rs @@ -0,0 +1,8 @@ +#[cfg(all(feature = "exec_feature", feature = "target_feature"))] +compile_error!("target and exec features were unified"); + +#[cfg(feature = "exec_feature")] +pub fn exec_only() {} + +#[cfg(feature = "target_feature")] +pub fn target_only() {}