From 3029dace483a9061ff4f3850da8451b12da00d82 Mon Sep 17 00:00:00 2001 From: UebelAndre Date: Tue, 26 Nov 2024 22:15:02 -0800 Subject: [PATCH] Added bzlmod support to core rules_rust --- .bazelci/presubmit.yml | 36 + .bazelrc | 4 - MODULE.bazel | 101 ++- cargo/extensions.bzl | 0 crate_universe/deps_bootstrap.bzl | 9 + crate_universe/docs_bzlmod.bzl | 10 +- crate_universe/extension.bzl | 603 +--------------- crate_universe/extensions.bzl | 675 ++++++++++++++++++ .../private/internal_extensions.bzl | 54 ++ .../private/module_extensions/BUILD.bazel | 13 - .../cargo_bazel_bootstrap.bzl | 69 -- .../tests/cargo_integration_test.rs | 2 - .../cross_installer/cross_installer_deps.bzl | 27 +- docs/src/crate_universe_bzlmod.md | 10 +- examples/bazel_env/MODULE.bazel | 2 +- examples/bzlmod/all_crate_deps/MODULE.bazel | 2 +- examples/bzlmod/all_deps_vendor/MODULE.bazel | 2 +- examples/bzlmod/all_deps_vendor/README.md | 32 +- examples/bzlmod/cross_compile/MODULE.bazel | 2 +- examples/bzlmod/hello_world/MODULE.bazel | 2 +- .../bzlmod/hello_world_no_cargo/MODULE.bazel | 2 +- examples/bzlmod/override_target/MODULE.bazel | 2 +- examples/bzlmod/proto/MODULE.bazel | 2 +- examples/bzlmod/proto/README.md | 2 +- .../bzlmod/proto_with_toolchain/MODULE.bazel | 2 +- .../bzlmod/proto_with_toolchain/README.md | 2 +- rust/extensions.bzl | 66 +- ...extensions.bzl => internal_extensions.bzl} | 2 - rust/private/rustc.bzl | 7 +- .../location_expansion/test.rs | 24 +- test/deps.bzl | 31 +- test/process_wrapper/BUILD.bazel | 4 + .../process_wrapper_tester.bzl | 39 +- test/process_wrapper/rustc_quit_on_rmeta.rs | 36 +- test/test_extensions.bzl | 19 +- test/unit/linkstamps/linkstamps_test.bzl | 9 +- test/unit/native_deps/native_deps_test.bzl | 7 +- 37 files changed, 1050 insertions(+), 861 deletions(-) create mode 100644 cargo/extensions.bzl create mode 100644 crate_universe/extensions.bzl create mode 100644 crate_universe/private/internal_extensions.bzl delete mode 100644 crate_universe/private/module_extensions/BUILD.bazel delete mode 100644 crate_universe/private/module_extensions/cargo_bazel_bootstrap.bzl rename rust/private/{extensions.bzl => internal_extensions.bzl} (91%) diff --git a/.bazelci/presubmit.yml b/.bazelci/presubmit.yml index 7fd964045b..8958eb37d5 100644 --- a/.bazelci/presubmit.yml +++ b/.bazelci/presubmit.yml @@ -16,6 +16,12 @@ bzlmod_flags: &bzlmod_flags bzlmod_plus_repo_names_flags: &bzlmod_plus_repo_names_flags # `--lockfile_mode=error` is omitted because the repo names leak into the lock file. - "--incompatible_use_plus_in_repo_names" +no_bzlmod_shell_commands: &no_bzlmod_shell_commands + - echo "common --noenable_bzlmod --enable_workspace" >> user.bazelrc +no_bzlmod_rbe_shell_commands: &no_bzlmod_rbe_shell_commands + - sed -i 's/^# load("@bazel_ci_rules/load("@bazel_ci_rules/' WORKSPACE.bazel + - sed -i 's/^# rbe_preconfig/rbe_preconfig/' WORKSPACE.bazel + - echo "common --noenable_bzlmod --enable_workspace" >> user.bazelrc single_rust_channel_targets: &single_rust_channel_targets - "--" - "//..." @@ -91,6 +97,36 @@ tasks: windows: build_targets: *default_windows_targets test_targets: *default_windows_targets + ubuntu2004_no_bzlmod: + name: No Bzlmod + platform: ubuntu2004 + shell_commands: *no_bzlmod_shell_commands + build_targets: *default_linux_targets + test_targets: *default_linux_targets + coverage_targets: *default_linux_targets + post_shell_commands: *coverage_validation_post_shell_commands + run_targets: + - //test:query_test_binary + rbe_ubuntu2004_no_bzlmod: + name: No Bzlmod + platform: rbe_ubuntu2004 + shell_commands: *no_bzlmod_rbe_shell_commands + build_targets: *default_linux_targets + test_targets: *default_linux_targets + macos_no_bzlmod: + name: No Bzlmod + platform: macos_arm64 + shell_commands: *no_bzlmod_shell_commands + build_targets: *default_macos_targets + test_targets: *default_macos_targets + coverage_targets: *default_macos_targets + post_shell_commands: *coverage_validation_post_shell_commands + windows_no_bzlmod: + name: No Bzlmod + platform: windows + shell_commands: *no_bzlmod_shell_commands + build_targets: *default_windows_targets + test_targets: *default_windows_targets windows_no_runfiles: name: No Runfiles platform: windows diff --git a/.bazelrc b/.bazelrc index 09a2ccea5c..45d5f5fddf 100644 --- a/.bazelrc +++ b/.bazelrc @@ -64,10 +64,6 @@ build --incompatible_merge_fixed_and_default_shell_env ## Bzlmod ############################################################################### -# TODO: migrate all dependencies from WORKSPACE to MODULE.bazel -# https://github.com/bazelbuild/rules_rust/issues/2181 -common --noenable_bzlmod --enable_workspace - # Disable the bzlmod lockfile, so we don't accidentally commit MODULE.bazel.lock common --lockfile_mode=off diff --git a/MODULE.bazel b/MODULE.bazel index ef6e4861ae..d4371d668d 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -5,6 +5,10 @@ module( version = "0.54.1", ) +############################################################################### +## Core +############################################################################### + bazel_dep( name = "bazel_features", version = "1.21.0", @@ -31,20 +35,75 @@ bazel_dep( repo_name = "build_bazel_apple_support", ) +internal_deps = use_extension("//rust/private:internal_extensions.bzl", "i") +use_repo( + internal_deps, + "rrra__anyhow-1.0.71", + "rrra__clap-4.3.11", + "rrra__env_logger-0.10.0", + "rrra__itertools-0.11.0", + "rrra__log-0.4.19", + "rrra__serde-1.0.171", + "rrra__serde_json-1.0.102", + "rules_rust_tinyjson", +) + +rust = use_extension("//rust:extensions.bzl", "rust") +rust.toolchain(edition = "2021") +use_repo(rust, "rust_toolchains") + +register_toolchains( + "@rust_toolchains//:all", +) + +rust_host_tools = use_extension("//rust:extensions.bzl", "rust_host_tools") +use_repo(rust_host_tools, "rust_host_tools") + +rust_test = use_extension("//test:test_extensions.bzl", "rust_test", dev_dependency = True) +use_repo( + rust_test, + "generated_inputs_in_external_repo", + "libc", + "rules_rust_test_load_arbitrary_tool", + "rules_rust_toolchain_test_target_json", + "t3p", + "t3p__serde-1.0.215", + "t3p__serde_json-1.0.133", +) + +bazel_dep( + name = "rules_python", + version = "0.40.0", + dev_dependency = True, +) bazel_dep( name = "rules_testing", - version = "0.6.0", + version = "0.7.0", + dev_dependency = True, +) +bazel_dep( + name = "bazel_ci_rules", + version = "1.0.0", dev_dependency = True, ) -internal_deps = use_extension("//rust/private:extensions.bzl", "i") +############################################################################### +## Crate Universe +############################################################################### + +crate_universe_internal_deps = use_extension( + "//crate_universe/private:internal_extensions.bzl", + "i", +) use_repo( - internal_deps, + crate_universe_internal_deps, "cargo_bazel.buildifier-darwin-amd64", "cargo_bazel.buildifier-darwin-arm64", "cargo_bazel.buildifier-linux-amd64", "cargo_bazel.buildifier-linux-arm64", + "cargo_bazel.buildifier-linux-s390x", "cargo_bazel.buildifier-windows-amd64.exe", + "cargo_bazel_bootstrap", "cui", "cui__anyhow-1.0.89", "cui__camino-1.1.9", @@ -76,37 +135,15 @@ use_repo( "cui__tracing-0.1.40", "cui__tracing-subscriber-0.3.18", "cui__url-2.5.2", - "rrra__anyhow-1.0.71", - "rrra__clap-4.3.11", - "rrra__env_logger-0.10.0", - "rrra__itertools-0.11.0", - "rrra__log-0.4.19", - "rrra__serde-1.0.171", - "rrra__serde_json-1.0.102", - "rules_rust_tinyjson", ) -rust = use_extension("//rust:extensions.bzl", "rust") -rust.toolchain(edition = "2021") -use_repo(rust, "rust_toolchains") - -register_toolchains( - "@rust_toolchains//:all", +crate_universe_internal_dev_deps = use_extension( + "//crate_universe/private:internal_extensions.bzl", + "i_dev", + dev_dependency = True, ) - -rust_host_tools = use_extension("//rust:extensions.bzl", "rust_host_tools") -use_repo(rust_host_tools, "rust_host_tools") - -cargo_bazel_bootstrap = use_extension("//crate_universe/private/module_extensions:cargo_bazel_bootstrap.bzl", "cargo_bazel_bootstrap") -use_repo(cargo_bazel_bootstrap, "cargo_bazel_bootstrap") - -rust_test = use_extension("//test:test_extensions.bzl", "rust_test", dev_dependency = True) use_repo( - rust_test, - "generated_inputs_in_external_repo", - "libc", - "rules_rust_test_load_arbitrary_tool", - "rules_rust_toolchain_test_target_json", - "t3p__serde-1.0.214", - "t3p__serde_json-1.0.132", + crate_universe_internal_dev_deps, + "cross_rs", + "cross_rs_host_bin", ) diff --git a/cargo/extensions.bzl b/cargo/extensions.bzl new file mode 100644 index 0000000000..e69de29bb2 diff --git a/crate_universe/deps_bootstrap.bzl b/crate_universe/deps_bootstrap.bzl index 615b42be4f..fa4e6a7e72 100644 --- a/crate_universe/deps_bootstrap.bzl +++ b/crate_universe/deps_bootstrap.bzl @@ -14,6 +14,10 @@ def cargo_bazel_bootstrap(name = "cargo_bazel_bootstrap", rust_version = rust_co name (str, optional): The name of the `cargo_bootstrap_repository`. rust_version (str, optional): The rust version to use. Defaults to the default of `cargo_bootstrap_repository`. **kwargs: kwargs to pass through to cargo_bootstrap_repository. + + Returns: + list[struct(repo=str, is_dev_dep=bool)]: A list of the repositories + defined by this macro. """ maybe( @@ -28,3 +32,8 @@ def cargo_bazel_bootstrap(name = "cargo_bazel_bootstrap", rust_version = rust_co timeout = 900, **kwargs ) + + return [struct( + repo = name, + is_dev_dep = False, + )] diff --git a/crate_universe/docs_bzlmod.bzl b/crate_universe/docs_bzlmod.bzl index 075f3d4a6e..4b51d656ba 100644 --- a/crate_universe/docs_bzlmod.bzl +++ b/crate_universe/docs_bzlmod.bzl @@ -35,7 +35,7 @@ You find the latest version on the [release page](https://github.com/bazelbuild/ After adding `rules_rust` in your MODULE.bazel, set the following to begin using `crate_universe`: ```starlark -crate = use_extension("@rules_rust//crate_universe:extension.bzl", "crate") +crate = use_extension("@rules_rust//crate_universe:extensions.bzl", "crate") // # ... Dependencies use_repo(crate, "crates") ``` @@ -55,7 +55,7 @@ The crates_repository rule can ingest a root Cargo.toml file and generate Bazel You find a complete example in the in the [example folder](../examples/bzlmod/all_crate_deps). ```starlark -crate = use_extension("@rules_rust//crate_universe:extension.bzl", "crate") +crate = use_extension("@rules_rust//crate_universe:extensions.bzl", "crate") crate.from_cargo( name = "crates", @@ -143,7 +143,7 @@ crates_repository supports this through the packages attribute, as shown below. ```starlark -crate = use_extension("@rules_rust//crate_universe:extension.bzl", "crate") +crate = use_extension("@rules_rust//crate_universe:extensions.bzl", "crate") crate.spec(package = "serde", features = ["derive"], version = "1.0") crate.spec(package = "serde_json", version = "1.0") @@ -218,7 +218,7 @@ register_toolchains("@rust_toolchains//:all") ############################################################################### # R U S T C R A T E S ############################################################################### -crate = use_extension("@rules_rust//crate_universe:extension.bzl", "crate") +crate = use_extension("@rules_rust//crate_universe:extensions.bzl", "crate") ``` Note, it is important to load the crate_universe rules otherwise you will get an error @@ -326,7 +326,7 @@ There are some more examples of using crate_universe with bzlmod in the [example """ load( - "//crate_universe:extension.bzl", + "//crate_universe:extensions.bzl", _crate = "crate", ) diff --git a/crate_universe/extension.bzl b/crate_universe/extension.bzl index 11c6ef64fd..75e89c880a 100644 --- a/crate_universe/extension.bzl +++ b/crate_universe/extension.bzl @@ -1,601 +1,8 @@ -"""Module extension for generating third-party crates for use in bazel.""" +"""Deprecated, use `:extensions.bzl`.""" -load("@bazel_features//:features.bzl", "bazel_features") -load("@bazel_skylib//lib:structs.bzl", "structs") -load("@bazel_tools//tools/build_defs/repo:git.bzl", "new_git_repository") -load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") -load("//crate_universe:defs.bzl", _crate_universe_crate = "crate") -load("//crate_universe/private:crates_vendor.bzl", "CRATES_VENDOR_ATTRS", "generate_config_file", "generate_splicing_manifest") -load("//crate_universe/private:generate_utils.bzl", "CARGO_BAZEL_GENERATOR_SHA256", "CARGO_BAZEL_GENERATOR_URL", "GENERATOR_ENV_VARS", "render_config") -load("//crate_universe/private:urls.bzl", "CARGO_BAZEL_SHA256S", "CARGO_BAZEL_URLS") -load("//crate_universe/private/module_extensions:cargo_bazel_bootstrap.bzl", "get_cargo_bazel_runner", "get_host_cargo_rustc") -load("//rust/platform:triple.bzl", "get_host_triple") - -# A list of labels which may be relative (and if so, is within the repo the rule is generated in). -# -# If I were to write ":foo", with attr.label_list, it would evaluate to -# "@@//:foo". However, for a tag such as deps, ":foo" should refer to -# "@@rules_rust~crates~//:foo". -_relative_label_list = attr.string_list - -_OPT_BOOL_VALUES = { - "auto": None, - "off": False, - "on": True, -} - -def optional_bool(doc): - return attr.string( - doc = doc, - values = _OPT_BOOL_VALUES.keys(), - default = "auto", - ) - -def _get_or_insert(d, key, value): - if key not in d: - d[key] = value - return d[key] - -def _generate_repo_impl(repo_ctx): - for path, contents in repo_ctx.attr.contents.items(): - repo_ctx.file(path, contents) - -_generate_repo = repository_rule( - implementation = _generate_repo_impl, - attrs = dict( - contents = attr.string_dict(mandatory = True), - ), -) - -def _annotations_for_repo(module_annotations, repo_specific_annotations): - """Merges the set of global annotations with the repo-specific ones - - Args: - module_annotations (dict): The annotation tags that apply to all repos, keyed by crate. - repo_specific_annotations (dict): The annotation tags that apply to only this repo, keyed by crate. - """ - - if not repo_specific_annotations: - return module_annotations - - annotations = dict(module_annotations) - for crate, values in repo_specific_annotations.items(): - _get_or_insert(annotations, crate, []).extend(values) - return annotations - -def _generate_hub_and_spokes(module_ctx, cargo_bazel, cfg, annotations, cargo_lockfile = None, manifests = {}, packages = {}): - """Generates repositories for the transitive closure of crates defined by manifests and packages. - - Args: - module_ctx (module_ctx): The module context object. - cargo_bazel (function): A function that can be called to execute cargo_bazel. - cfg (object): The module tag from `from_cargo` or `from_specs` - annotations (dict): The set of annotation tag classes that apply to this closure, keyed by crate name. - cargo_lockfile (path): Path to Cargo.lock, if we have one. This is optional for `from_specs` closures. - manifests (dict): The set of Cargo.toml manifests that apply to this closure, if any, keyed by path. - packages (dict): The set of extra cargo crate tags that apply to this closure, if any, keyed by package name. - """ - - tag_path = module_ctx.path(cfg.name) - - rendering_config = json.decode(render_config( - regen_command = "Run 'cargo update [--workspace]'", - )) - config_file = tag_path.get_child("config.json") - module_ctx.file( - config_file, - executable = False, - content = generate_config_file( - module_ctx, - mode = "remote", - annotations = annotations, - generate_build_scripts = cfg.generate_build_scripts, - supported_platform_triples = cfg.supported_platform_triples, - generate_target_compatible_with = True, - repository_name = cfg.name, - output_pkg = cfg.name, - workspace_name = cfg.name, - generate_binaries = cfg.generate_binaries, - render_config = rendering_config, - repository_ctx = module_ctx, - ), - ) - - splicing_manifest = tag_path.get_child("splicing_manifest.json") - module_ctx.file( - splicing_manifest, - executable = False, - content = generate_splicing_manifest( - packages = packages, - splicing_config = cfg.splicing_config, - cargo_config = cfg.cargo_config, - manifests = manifests, - manifest_to_path = module_ctx.path, - ), - ) - - splicing_output_dir = tag_path.get_child("splicing-output") - splice_args = [ - "splice", - "--output-dir", - splicing_output_dir, - "--config", - config_file, - "--splicing-manifest", - splicing_manifest, - ] - if cargo_lockfile: - splice_args.extend([ - "--cargo-lockfile", - cargo_lockfile, - ]) - cargo_bazel(splice_args) - - # Create a lockfile, since we need to parse it to generate spoke - # repos. - lockfile_path = tag_path.get_child("lockfile.json") - module_ctx.file(lockfile_path, "") - - cargo_bazel([ - "generate", - "--cargo-lockfile", - cargo_lockfile or splicing_output_dir.get_child("Cargo.lock"), - "--config", - config_file, - "--splicing-manifest", - splicing_manifest, - "--repository-dir", - tag_path, - "--metadata", - splicing_output_dir.get_child("metadata.json"), - "--repin", - "--lockfile", - lockfile_path, - ]) - - crates_dir = tag_path.get_child(cfg.name) - _generate_repo( - name = cfg.name, - contents = { - "BUILD.bazel": module_ctx.read(crates_dir.get_child("BUILD.bazel")), - "defs.bzl": module_ctx.read(crates_dir.get_child("defs.bzl")), - }, - ) - - contents = json.decode(module_ctx.read(lockfile_path)) - - for crate in contents["crates"].values(): - repo = crate["repository"] - if repo == None: - continue - name = crate["name"] - version = crate["version"] - - # "+" isn't valid in a repo name. - crate_repo_name = "{repo_name}__{name}-{version}".format( - repo_name = cfg.name, - name = name, - version = version.replace("+", "-"), - ) - - build_file_content = module_ctx.read(crates_dir.get_child("BUILD.%s-%s.bazel" % (name, version))) - if "Http" in repo: - # Replicates functionality in repo_http.j2. - repo = repo["Http"] - http_archive( - name = crate_repo_name, - patch_args = repo.get("patch_args", None), - patch_tool = repo.get("patch_tool", None), - patches = repo.get("patches", None), - remote_patch_strip = 1, - sha256 = repo.get("sha256", None), - type = "tar.gz", - urls = [repo["url"]], - strip_prefix = "%s-%s" % (crate["name"], crate["version"]), - build_file_content = build_file_content, - ) - elif "Git" in repo: - # Replicates functionality in repo_git.j2 - repo = repo["Git"] - kwargs = {} - for k, v in repo["commitish"].items(): - if k == "Rev": - kwargs["commit"] = v - else: - kwargs[k.lower()] = v - new_git_repository( - name = crate_repo_name, - init_submodules = True, - patch_args = repo.get("patch_args", None), - patch_tool = repo.get("patch_tool", None), - patches = repo.get("patches", None), - shallow_since = repo.get("shallow_since", None), - remote = repo["remote"], - build_file_content = build_file_content, - strip_prefix = repo.get("strip_prefix", None), - **kwargs - ) - else: - fail("Invalid repo: expected Http or Git to exist for crate %s-%s, got %s" % (name, version, repo)) - -def _package_to_json(p): - # Avoid adding unspecified properties. - # If we add them as empty strings, cargo-bazel will be unhappy. - return json.encode({ - k: v - for k, v in structs.to_dict(p).items() - if v or k == "default_features" - }) - -def _get_generator(module_ctx): - """Query Network Resources to local a `cargo-bazel` binary. - - Based off get_generator in crates_universe/private/generate_utils.bzl - - Args: - module_ctx (module_ctx): The rules context object - - Returns: - tuple(path, dict) The path to a 'cargo-bazel' binary. The pairing (dict) - may be `None` if there is not need to update the attribute - """ - host_triple = get_host_triple(module_ctx) - use_environ = False - for var in GENERATOR_ENV_VARS: - if var in module_ctx.os.environ: - use_environ = True - - if use_environ: - generator_sha256 = module_ctx.os.environ.get(CARGO_BAZEL_GENERATOR_SHA256) - generator_url = module_ctx.os.environ.get(CARGO_BAZEL_GENERATOR_URL) - elif len(CARGO_BAZEL_URLS) == 0: - return module_ctx.path(Label("@cargo_bazel_bootstrap//:cargo-bazel")) - else: - generator_sha256 = CARGO_BAZEL_SHA256S.get(host_triple.str) - generator_url = CARGO_BAZEL_URLS.get(host_triple.str) - - if not generator_url: - fail(( - "No generator URL was found either in the `CARGO_BAZEL_GENERATOR_URL` " + - "environment variable or for the `{}` triple in the `generator_urls` attribute" - ).format(host_triple.str)) - - output = module_ctx.path("cargo-bazel.exe" if "win" in module_ctx.os.name else "cargo-bazel") - - # Download the file into place - download_kwargs = { - "executable": True, - "output": output, - "url": generator_url, - } - - if generator_sha256: - download_kwargs.update({"sha256": generator_sha256}) - - module_ctx.download(**download_kwargs) - return output - -def _crate_impl(module_ctx): - # Preload all external repositories. Calling `module_ctx.path` will cause restarts of the implementation - # function of the module extension, so we want to trigger all restarts before we start the actual work. - # Once https://github.com/bazelbuild/bazel/issues/22729 has been fixed, this code can be removed. - get_host_cargo_rustc(module_ctx) - for mod in module_ctx.modules: - for cfg in mod.tags.from_cargo: - module_ctx.path(cfg.cargo_lockfile) - for m in cfg.manifests: - module_ctx.path(m) - - cargo_bazel_output = _get_generator(module_ctx) - cargo_bazel = get_cargo_bazel_runner(module_ctx, cargo_bazel_output) - - all_repos = [] - reproducible = True - - for mod in module_ctx.modules: - module_annotations = {} - repo_specific_annotations = {} - for annotation_tag in mod.tags.annotation: - annotation_dict = structs.to_dict(annotation_tag) - repositories = annotation_dict.pop("repositories") - crate = annotation_dict.pop("crate") - - # The crate.annotation function can take in either a list or a bool. - # For the tag-based method, because it has type safety, we have to - # split it into two parameters. - if annotation_dict.pop("gen_all_binaries"): - annotation_dict["gen_binaries"] = True - annotation_dict["gen_build_script"] = _OPT_BOOL_VALUES[annotation_dict["gen_build_script"]] - - # Process the override targets for the annotation. - # In the non-bzlmod approach, this is given as a dict - # with the possible keys "`proc_macro`, `build_script`, `lib`, `bin`". - # With the tag-based approach used in Bzlmod, we run into an issue - # where there is no dict type that takes a string as a key and a Label as the value. - # To work around this, we split the override option into four, and reconstruct the - # dictionary here during processing - annotation_dict["override_targets"] = dict() - replacement = annotation_dict.pop("override_target_lib") - if replacement: - annotation_dict["override_targets"]["lib"] = str(replacement) - - replacement = annotation_dict.pop("override_target_proc_macro") - if replacement: - annotation_dict["override_targets"]["proc_macro"] = str(replacement) - - replacement = annotation_dict.pop("override_target_build_script") - if replacement: - annotation_dict["override_targets"]["build_script"] = str(replacement) - - replacement = annotation_dict.pop("override_target_bin") - if replacement: - annotation_dict["override_targets"]["bin"] = str(replacement) - - annotation = _crate_universe_crate.annotation(**{ - k: v - for k, v in annotation_dict.items() - # Tag classes can't take in None, but the function requires None - # instead of the empty values in many cases. - # https://github.com/bazelbuild/bazel/issues/20744 - if v != "" and v != [] and v != {} - }) - if not repositories: - _get_or_insert(module_annotations, crate, []).append(annotation) - for repo in repositories: - _get_or_insert( - _get_or_insert(repo_specific_annotations, repo, {}), - crate, - [], - ).append(annotation) - - local_repos = [] - - for cfg in mod.tags.from_cargo + mod.tags.from_specs: - if cfg.name in local_repos: - fail("Defined two crate universes with the same name in the same MODULE.bazel file. Use the name tag to give them different names.") - elif cfg.name in all_repos: - 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)") - all_repos.append(cfg.name) - local_repos.append(cfg.name) - - for cfg in mod.tags.from_cargo: - annotations = _annotations_for_repo( - module_annotations, - repo_specific_annotations.get(cfg.name), - ) - - cargo_lockfile = module_ctx.path(cfg.cargo_lockfile) - manifests = {str(module_ctx.path(m)): str(m) for m in cfg.manifests} - _generate_hub_and_spokes(module_ctx, cargo_bazel, cfg, annotations, cargo_lockfile = cargo_lockfile, manifests = manifests) - - for cfg in mod.tags.from_specs: - # We don't have a Cargo.lock so the resolution can change. - # We could maybe make this reproducible by using `-minimal-version` during resolution. - # See https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#minimal-versions - reproducible = False - - annotations = _annotations_for_repo( - module_annotations, - repo_specific_annotations.get(cfg.name), - ) - - packages = {p.package: _package_to_json(p) for p in mod.tags.spec} - _generate_hub_and_spokes(module_ctx, cargo_bazel, cfg, annotations, packages = packages) - - for repo in repo_specific_annotations: - if repo not in local_repos: - fail("Annotation specified for repo %s, but the module defined repositories %s" % (repo, local_repos)) - - metadata_kwargs = {} - if bazel_features.external_deps.extension_metadata_has_reproducible: - metadata_kwargs["reproducible"] = reproducible - - return module_ctx.extension_metadata(**metadata_kwargs) - -_from_cargo = tag_class( - doc = "Generates a repo @crates from a Cargo.toml / Cargo.lock pair", - attrs = dict( - name = attr.string( - doc = "The name of the repo to generate", - default = "crates", - ), - cargo_lockfile = CRATES_VENDOR_ATTRS["cargo_lockfile"], - manifests = CRATES_VENDOR_ATTRS["manifests"], - cargo_config = CRATES_VENDOR_ATTRS["cargo_config"], - generate_binaries = CRATES_VENDOR_ATTRS["generate_binaries"], - generate_build_scripts = CRATES_VENDOR_ATTRS["generate_build_scripts"], - splicing_config = CRATES_VENDOR_ATTRS["splicing_config"], - supported_platform_triples = CRATES_VENDOR_ATTRS["supported_platform_triples"], - ), +load( + ":extensions.bzl", + _crate = "crate", ) -# This should be kept in sync with crate_universe/private/crate.bzl. -_annotation = tag_class( - attrs = dict( - repositories = attr.string_list( - doc = "A list of repository names specified from `crate.from_cargo(name=...)` that this annotation is applied to. Defaults to all repositories.", - default = [], - ), - crate = attr.string( - doc = "The name of the crate the annotation is applied to", - mandatory = True, - ), - version = attr.string( - doc = "The versions of the crate the annotation is applied to. Defaults to all versions.", - default = "*", - ), - additive_build_file_content = attr.string( - doc = "Extra contents to write to the bottom of generated BUILD files.", - ), - additive_build_file = attr.label( - doc = "A file containing extra contents to write to the bottom of generated BUILD files.", - ), - alias_rule = attr.string( - doc = "Alias rule to use instead of `native.alias()`. Overrides [render_config](#render_config)'s 'default_alias_rule'.", - ), - build_script_data = _relative_label_list( - doc = "A list of labels to add to a crate's `cargo_build_script::data` attribute.", - ), - build_script_tools = _relative_label_list( - doc = "A list of labels to add to a crate's `cargo_build_script::tools` attribute.", - ), - build_script_data_glob = attr.string_list( - doc = "A list of glob patterns to add to a crate's `cargo_build_script::data` attribute", - ), - build_script_deps = _relative_label_list( - doc = "A list of labels to add to a crate's `cargo_build_script::deps` attribute.", - ), - build_script_env = attr.string_dict( - doc = "Additional environment variables to set on a crate's `cargo_build_script::env` attribute.", - ), - build_script_proc_macro_deps = _relative_label_list( - doc = "A list of labels to add to a crate's `cargo_build_script::proc_macro_deps` attribute.", - ), - build_script_rundir = attr.string( - doc = "An override for the build script's rundir attribute.", - ), - build_script_rustc_env = attr.string_dict( - doc = "Additional environment variables to set on a crate's `cargo_build_script::env` attribute.", - ), - build_script_toolchains = attr.label_list( - doc = "A list of labels to set on a crates's `cargo_build_script::toolchains` attribute.", - ), - compile_data = _relative_label_list( - doc = "A list of labels to add to a crate's `rust_library::compile_data` attribute.", - ), - compile_data_glob = attr.string_list( - doc = "A list of glob patterns to add to a crate's `rust_library::compile_data` attribute.", - ), - crate_features = attr.string_list( - doc = "A list of strings to add to a crate's `rust_library::crate_features` attribute.", - ), - data = _relative_label_list( - doc = "A list of labels to add to a crate's `rust_library::data` attribute.", - ), - data_glob = attr.string_list( - doc = "A list of glob patterns to add to a crate's `rust_library::data` attribute.", - ), - deps = _relative_label_list( - doc = "A list of labels to add to a crate's `rust_library::deps` attribute.", - ), - extra_aliased_targets = attr.string_dict( - doc = "A list of targets to add to the generated aliases in the root crate_universe repository.", - ), - gen_binaries = attr.string_list( - doc = "As a list, the subset of the crate's bins that should get `rust_binary` targets produced.", - ), - gen_all_binaries = attr.bool( - doc = "If true, generates `rust_binary` targets for all of the crates bins", - ), - disable_pipelining = attr.bool( - doc = "If True, disables pipelining for library targets for this crate.", - ), - gen_build_script = attr.string( - 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'.", - values = _OPT_BOOL_VALUES.keys(), - default = "auto", - ), - patch_args = attr.string_list( - 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)", - ), - patch_tool = attr.string( - 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)", - ), - patches = attr.label_list( - 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)", - ), - proc_macro_deps = _relative_label_list( - doc = "A list of labels to add to a crate's `rust_library::proc_macro_deps` attribute.", - ), - rustc_env = attr.string_dict( - doc = "Additional variables to set on a crate's `rust_library::rustc_env` attribute.", - ), - rustc_env_files = _relative_label_list( - doc = "A list of labels to set on a crate's `rust_library::rustc_env_files` attribute.", - ), - rustc_flags = attr.string_list( - doc = "A list of strings to set on a crate's `rust_library::rustc_flags` attribute.", - ), - shallow_since = attr.string( - doc = "An optional timestamp used for crates originating from a git repository instead of a crate registry. This flag optimizes fetching the source code.", - ), - override_target_lib = attr.label( - 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.", - ), - override_target_proc_macro = attr.label( - 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.", - ), - override_target_build_script = attr.label( - 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.", - ), - override_target_bin = attr.label( - 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.", - ), - ), -) - -_from_specs = tag_class( - doc = "Generates a repo @crates from the defined `spec` tags", - attrs = dict( - name = attr.string(doc = "The name of the repo to generate", default = "crates"), - cargo_config = CRATES_VENDOR_ATTRS["cargo_config"], - generate_binaries = CRATES_VENDOR_ATTRS["generate_binaries"], - generate_build_scripts = CRATES_VENDOR_ATTRS["generate_build_scripts"], - splicing_config = CRATES_VENDOR_ATTRS["splicing_config"], - supported_platform_triples = CRATES_VENDOR_ATTRS["supported_platform_triples"], - ), -) - -# This should be kept in sync with crate_universe/private/crate.bzl. -_spec = tag_class( - attrs = dict( - package = attr.string( - doc = "The explicit name of the package.", - mandatory = True, - ), - version = attr.string( - doc = "The exact version of the crate. Cannot be used with `git`.", - ), - artifact = attr.string( - doc = "Set to 'bin' to pull in a binary crate as an artifact dependency. Requires a nightly Cargo.", - ), - lib = attr.bool( - 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.", - ), - default_features = attr.bool( - doc = "Maps to the `default-features` flag.", - default = True, - ), - features = attr.string_list( - doc = "A list of features to use for the crate.", - ), - git = attr.string( - doc = "The Git url to use for the crate. Cannot be used with `version`.", - ), - branch = attr.string( - 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.", - ), - tag = attr.string( - 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.", - ), - rev = attr.string( - doc = "The git revision of the remote crate. Tied with the `git` param. Only one of branch, tag or rev may be specified.", - ), - ), -) - -_conditional_crate_args = { - "arch_dependent": True, - "os_dependent": True, -} if bazel_features.external_deps.module_extension_has_os_arch_dependent else {} - -crate = module_extension( - implementation = _crate_impl, - tag_classes = dict( - from_cargo = _from_cargo, - annotation = _annotation, - from_specs = _from_specs, - spec = _spec, - ), - **_conditional_crate_args -) +crate = _crate diff --git a/crate_universe/extensions.bzl b/crate_universe/extensions.bzl new file mode 100644 index 0000000000..feb7d6188b --- /dev/null +++ b/crate_universe/extensions.bzl @@ -0,0 +1,675 @@ +"""Module extension for generating third-party crates for use in bazel.""" + +load("@bazel_features//:features.bzl", "bazel_features") +load("@bazel_skylib//lib:structs.bzl", "structs") +load("@bazel_tools//tools/build_defs/repo:git.bzl", "new_git_repository") +load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") +load("//crate_universe/private:crates_vendor.bzl", "CRATES_VENDOR_ATTRS", "generate_config_file", "generate_splicing_manifest") +load("//crate_universe/private:generate_utils.bzl", "CARGO_BAZEL_GENERATOR_SHA256", "CARGO_BAZEL_GENERATOR_URL", "GENERATOR_ENV_VARS", "render_config") +load("//crate_universe/private:urls.bzl", "CARGO_BAZEL_SHA256S", "CARGO_BAZEL_URLS") +load("//rust/platform:triple.bzl", "get_host_triple") +load("//rust/platform:triple_mappings.bzl", "system_to_binary_ext") +load(":defs.bzl", _crate_universe_crate = "crate") + +# A list of labels which may be relative (and if so, is within the repo the rule is generated in). +# +# If I were to write ":foo", with attr.label_list, it would evaluate to +# "@@//:foo". However, for a tag such as deps, ":foo" should refer to +# "@@rules_rust~crates~//:foo". +_relative_label_list = attr.string_list + +_OPT_BOOL_VALUES = { + "auto": None, + "off": False, + "on": True, +} + +def optional_bool(doc): + return attr.string( + doc = doc, + values = _OPT_BOOL_VALUES.keys(), + default = "auto", + ) + +def _get_or_insert(d, key, value): + if key not in d: + d[key] = value + return d[key] + +def _generate_repo_impl(repo_ctx): + for path, contents in repo_ctx.attr.contents.items(): + repo_ctx.file(path, contents) + +_generate_repo = repository_rule( + doc = "TODO", + implementation = _generate_repo_impl, + attrs = { + "contents": attr.string_dict( + doc = "TODO", + mandatory = True, + ), + }, +) + +def _annotations_for_repo(module_annotations, repo_specific_annotations): + """Merges the set of global annotations with the repo-specific ones + + Args: + module_annotations (dict): The annotation tags that apply to all repos, keyed by crate. + repo_specific_annotations (dict): The annotation tags that apply to only this repo, keyed by crate. + """ + + if not repo_specific_annotations: + return module_annotations + + annotations = dict(module_annotations) + for crate, values in repo_specific_annotations.items(): + _get_or_insert(annotations, crate, []).extend(values) + return annotations + +def _generate_hub_and_spokes(*, module_ctx, cargo_bazel, cfg, annotations, cargo_lockfile = None, manifests = {}, packages = {}): + """Generates repositories for the transitive closure of crates defined by manifests and packages. + + Args: + module_ctx (module_ctx): The module context object. + cargo_bazel (function): A function that can be called to execute cargo_bazel. + cfg (object): The module tag from `from_cargo` or `from_specs` + annotations (dict): The set of annotation tag classes that apply to this closure, keyed by crate name. + cargo_lockfile (path): Path to Cargo.lock, if we have one. This is optional for `from_specs` closures. + manifests (dict): The set of Cargo.toml manifests that apply to this closure, if any, keyed by path. + packages (dict): The set of extra cargo crate tags that apply to this closure, if any, keyed by package name. + """ + + tag_path = module_ctx.path(cfg.name) + + rendering_config = json.decode(render_config( + regen_command = "Run 'cargo update [--workspace]'", + )) + config_file = tag_path.get_child("config.json") + module_ctx.file( + config_file, + executable = False, + content = generate_config_file( + module_ctx, + mode = "remote", + annotations = annotations, + generate_build_scripts = cfg.generate_build_scripts, + supported_platform_triples = cfg.supported_platform_triples, + generate_target_compatible_with = True, + repository_name = cfg.name, + output_pkg = cfg.name, + workspace_name = cfg.name, + generate_binaries = cfg.generate_binaries, + render_config = rendering_config, + repository_ctx = module_ctx, + ), + ) + + splicing_manifest = tag_path.get_child("splicing_manifest.json") + module_ctx.file( + splicing_manifest, + executable = False, + content = generate_splicing_manifest( + packages = packages, + splicing_config = cfg.splicing_config, + cargo_config = cfg.cargo_config, + manifests = manifests, + manifest_to_path = module_ctx.path, + ), + ) + + splicing_output_dir = tag_path.get_child("splicing-output") + splice_args = [ + "splice", + "--output-dir", + splicing_output_dir, + "--config", + config_file, + "--splicing-manifest", + splicing_manifest, + ] + if cargo_lockfile: + splice_args.extend([ + "--cargo-lockfile", + cargo_lockfile, + ]) + cargo_bazel(splice_args) + + # Create a lockfile, since we need to parse it to generate spoke + # repos. + lockfile_path = tag_path.get_child("lockfile.json") + module_ctx.file(lockfile_path, "") + + cargo_bazel([ + "generate", + "--cargo-lockfile", + cargo_lockfile or splicing_output_dir.get_child("Cargo.lock"), + "--config", + config_file, + "--splicing-manifest", + splicing_manifest, + "--repository-dir", + tag_path, + "--metadata", + splicing_output_dir.get_child("metadata.json"), + "--repin", + "--lockfile", + lockfile_path, + ]) + + crates_dir = tag_path.get_child(cfg.name) + _generate_repo( + name = cfg.name, + contents = { + "BUILD.bazel": module_ctx.read(crates_dir.get_child("BUILD.bazel")), + "defs.bzl": module_ctx.read(crates_dir.get_child("defs.bzl")), + }, + ) + + contents = json.decode(module_ctx.read(lockfile_path)) + + for crate in contents["crates"].values(): + repo = crate["repository"] + if repo == None: + continue + name = crate["name"] + version = crate["version"] + + # "+" isn't valid in a repo name. + crate_repo_name = "{repo_name}__{name}-{version}".format( + repo_name = cfg.name, + name = name, + version = version.replace("+", "-"), + ) + + build_file_content = module_ctx.read(crates_dir.get_child("BUILD.%s-%s.bazel" % (name, version))) + if "Http" in repo: + # Replicates functionality in repo_http.j2. + repo = repo["Http"] + http_archive( + name = crate_repo_name, + patch_args = repo.get("patch_args", None), + patch_tool = repo.get("patch_tool", None), + patches = repo.get("patches", None), + remote_patch_strip = 1, + sha256 = repo.get("sha256", None), + type = "tar.gz", + urls = [repo["url"]], + strip_prefix = "%s-%s" % (crate["name"], crate["version"]), + build_file_content = build_file_content, + ) + elif "Git" in repo: + # Replicates functionality in repo_git.j2 + repo = repo["Git"] + kwargs = {} + for k, v in repo["commitish"].items(): + if k == "Rev": + kwargs["commit"] = v + else: + kwargs[k.lower()] = v + new_git_repository( + name = crate_repo_name, + init_submodules = True, + patch_args = repo.get("patch_args", None), + patch_tool = repo.get("patch_tool", None), + patches = repo.get("patches", None), + shallow_since = repo.get("shallow_since", None), + remote = repo["remote"], + build_file_content = build_file_content, + strip_prefix = repo.get("strip_prefix", None), + **kwargs + ) + else: + fail("Invalid repo: expected Http or Git to exist for crate %s-%s, got %s" % (name, version, repo)) + +def _package_to_json(p): + # Avoid adding unspecified properties. + # If we add them as empty strings, cargo-bazel will be unhappy. + return json.encode({ + k: v + for k, v in structs.to_dict(p).items() + if v or k == "default_features" + }) + +def _get_generator(module_ctx): + """Query Network Resources to local a `cargo-bazel` binary. + + Based off get_generator in crates_universe/private/generate_utils.bzl + + Args: + module_ctx (module_ctx): The rules context object + + Returns: + tuple(path, dict) The path to a 'cargo-bazel' binary. The pairing (dict) + may be `None` if there is not need to update the attribute + """ + host_triple = get_host_triple(module_ctx) + use_environ = False + for var in GENERATOR_ENV_VARS: + if var in module_ctx.os.environ: + use_environ = True + + if use_environ: + generator_sha256 = module_ctx.os.environ.get(CARGO_BAZEL_GENERATOR_SHA256) + generator_url = module_ctx.os.environ.get(CARGO_BAZEL_GENERATOR_URL) + elif len(CARGO_BAZEL_URLS) == 0: + return module_ctx.path(Label("@cargo_bazel_bootstrap//:cargo-bazel")) + else: + generator_sha256 = CARGO_BAZEL_SHA256S.get(host_triple.str) + generator_url = CARGO_BAZEL_URLS.get(host_triple.str) + + if not generator_url: + fail(( + "No generator URL was found either in the `CARGO_BAZEL_GENERATOR_URL` " + + "environment variable or for the `{}` triple in the `generator_urls` attribute" + ).format(host_triple.str)) + + output = module_ctx.path("cargo-bazel.exe" if "win" in module_ctx.os.name else "cargo-bazel") + + # Download the file into place + download_kwargs = { + "executable": True, + "output": output, + "url": generator_url, + } + + if generator_sha256: + download_kwargs.update({"sha256": generator_sha256}) + + module_ctx.download(**download_kwargs) + return output + +def _get_host_cargo_rustc(module_ctx): + """A helper function to get the path to the host cargo and rustc binaries. + + Args: + module_ctx: The module extension's context. + Returns: + A tuple of path to cargo, path to rustc. + + """ + host_triple = get_host_triple(module_ctx) + binary_ext = system_to_binary_ext(host_triple.system) + + cargo_path = str(module_ctx.path(Label("@rust_host_tools//:bin/cargo{}".format(binary_ext)))) + rustc_path = str(module_ctx.path(Label("@rust_host_tools//:bin/rustc{}".format(binary_ext)))) + return cargo_path, rustc_path + +def _get_cargo_bazel_runner(module_ctx, cargo_bazel): + """A helper function to allow executing cargo_bazel in module extensions. + + Args: + module_ctx: The module extension's context. + cargo_bazel: Path The path to a `cargo-bazel` binary + Returns: + A function that can be called to execute cargo_bazel. + """ + cargo_path, rustc_path = _get_host_cargo_rustc(module_ctx) + + # Placing this as a nested function allows users to call this right at the + # start of a module extension, thus triggering any restarts as early as + # possible (since module_ctx.path triggers restarts). + def run(args, env = {}, timeout = 600): + final_args = [cargo_bazel] + final_args.extend(args) + final_args.extend([ + "--cargo", + cargo_path, + "--rustc", + rustc_path, + ]) + result = module_ctx.execute( + final_args, + environment = dict(CARGO = cargo_path, RUSTC = rustc_path, **env), + timeout = timeout, + ) + if result.return_code != 0: + if result.stdout: + print("Stdout:", result.stdout) # buildifier: disable=print + pretty_args = " ".join([str(arg) for arg in final_args]) + fail("%s returned with exit code %d:\n%s" % (pretty_args, result.return_code, result.stderr)) + return result + + return run + +def _crate_impl(module_ctx): + # Preload all external repositories. Calling `module_ctx.path` will cause restarts of the implementation + # function of the module extension, so we want to trigger all restarts before we start the actual work. + # Once https://github.com/bazelbuild/bazel/issues/22729 has been fixed, this code can be removed. + _get_host_cargo_rustc(module_ctx) + for mod in module_ctx.modules: + for cfg in mod.tags.from_cargo: + module_ctx.path(cfg.cargo_lockfile) + for m in cfg.manifests: + module_ctx.path(m) + + cargo_bazel_output = _get_generator(module_ctx) + cargo_bazel = _get_cargo_bazel_runner(module_ctx, cargo_bazel_output) + + all_repos = [] + reproducible = True + + for mod in module_ctx.modules: + module_annotations = {} + repo_specific_annotations = {} + for annotation_tag in mod.tags.annotation: + annotation_dict = structs.to_dict(annotation_tag) + repositories = annotation_dict.pop("repositories") + crate = annotation_dict.pop("crate") + + # The crate.annotation function can take in either a list or a bool. + # For the tag-based method, because it has type safety, we have to + # split it into two parameters. + if annotation_dict.pop("gen_all_binaries"): + annotation_dict["gen_binaries"] = True + annotation_dict["gen_build_script"] = _OPT_BOOL_VALUES[annotation_dict["gen_build_script"]] + + # Process the override targets for the annotation. + # In the non-bzlmod approach, this is given as a dict + # with the possible keys "`proc_macro`, `build_script`, `lib`, `bin`". + # With the tag-based approach used in Bzlmod, we run into an issue + # where there is no dict type that takes a string as a key and a Label as the value. + # To work around this, we split the override option into four, and reconstruct the + # dictionary here during processing + annotation_dict["override_targets"] = dict() + replacement = annotation_dict.pop("override_target_lib") + if replacement: + annotation_dict["override_targets"]["lib"] = str(replacement) + + replacement = annotation_dict.pop("override_target_proc_macro") + if replacement: + annotation_dict["override_targets"]["proc_macro"] = str(replacement) + + replacement = annotation_dict.pop("override_target_build_script") + if replacement: + annotation_dict["override_targets"]["build_script"] = str(replacement) + + replacement = annotation_dict.pop("override_target_bin") + if replacement: + annotation_dict["override_targets"]["bin"] = str(replacement) + + annotation = _crate_universe_crate.annotation(**{ + k: v + for k, v in annotation_dict.items() + # Tag classes can't take in None, but the function requires None + # instead of the empty values in many cases. + # https://github.com/bazelbuild/bazel/issues/20744 + if v != "" and v != [] and v != {} + }) + if not repositories: + _get_or_insert(module_annotations, crate, []).append(annotation) + for repo in repositories: + _get_or_insert( + _get_or_insert(repo_specific_annotations, repo, {}), + crate, + [], + ).append(annotation) + + local_repos = [] + + for cfg in mod.tags.from_cargo + mod.tags.from_specs: + if cfg.name in local_repos: + fail("Defined two crate universes with the same name in the same MODULE.bazel file. Use the name tag to give them different names.") + elif cfg.name in all_repos: + 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)") + all_repos.append(cfg.name) + local_repos.append(cfg.name) + + for cfg in mod.tags.from_cargo: + annotations = _annotations_for_repo( + module_annotations, + repo_specific_annotations.get(cfg.name), + ) + + cargo_lockfile = module_ctx.path(cfg.cargo_lockfile) + manifests = {str(module_ctx.path(m)): str(m) for m in cfg.manifests} + _generate_hub_and_spokes( + module_ctx = module_ctx, + cargo_bazel = cargo_bazel, + cfg = cfg, + annotations = annotations, + cargo_lockfile = cargo_lockfile, + manifests = manifests, + ) + + for cfg in mod.tags.from_specs: + # We don't have a Cargo.lock so the resolution can change. + # We could maybe make this reproducible by using `-minimal-version` during resolution. + # See https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#minimal-versions + reproducible = False + + annotations = _annotations_for_repo( + module_annotations, + repo_specific_annotations.get(cfg.name), + ) + + packages = {p.package: _package_to_json(p) for p in mod.tags.spec} + _generate_hub_and_spokes( + module_ctx = module_ctx, + cargo_bazel = cargo_bazel, + cfg = cfg, + annotations = annotations, + packages = packages, + ) + + for repo in repo_specific_annotations: + if repo not in local_repos: + fail("Annotation specified for repo %s, but the module defined repositories %s" % (repo, local_repos)) + + metadata_kwargs = {} + if bazel_features.external_deps.extension_metadata_has_reproducible: + metadata_kwargs["reproducible"] = reproducible + + return module_ctx.extension_metadata(**metadata_kwargs) + +_from_cargo = tag_class( + doc = "Generates a repo @crates from a Cargo.toml / Cargo.lock pair.", + attrs = { + "cargo_config": CRATES_VENDOR_ATTRS["cargo_config"], + "cargo_lockfile": CRATES_VENDOR_ATTRS["cargo_lockfile"], + "generate_binaries": CRATES_VENDOR_ATTRS["generate_binaries"], + "generate_build_scripts": CRATES_VENDOR_ATTRS["generate_build_scripts"], + "manifests": CRATES_VENDOR_ATTRS["manifests"], + "name": attr.string( + doc = "The name of the repo to generate", + default = "crates", + ), + "splicing_config": CRATES_VENDOR_ATTRS["splicing_config"], + "supported_platform_triples": CRATES_VENDOR_ATTRS["supported_platform_triples"], + }, +) + +# This should be kept in sync with crate_universe/private/crate.bzl. +_annotation = tag_class( + attrs = { + "additive_build_file": attr.label( + doc = "A file containing extra contents to write to the bottom of generated BUILD files.", + ), + "additive_build_file_content": attr.string( + doc = "Extra contents to write to the bottom of generated BUILD files.", + ), + "alias_rule": attr.string( + doc = "Alias rule to use instead of `native.alias()`. Overrides [render_config](#render_config)'s 'default_alias_rule'.", + ), + "build_script_data": _relative_label_list( + doc = "A list of labels to add to a crate's `cargo_build_script::data` attribute.", + ), + "build_script_data_glob": attr.string_list( + doc = "A list of glob patterns to add to a crate's `cargo_build_script::data` attribute", + ), + "build_script_deps": _relative_label_list( + doc = "A list of labels to add to a crate's `cargo_build_script::deps` attribute.", + ), + "build_script_env": attr.string_dict( + doc = "Additional environment variables to set on a crate's `cargo_build_script::env` attribute.", + ), + "build_script_proc_macro_deps": _relative_label_list( + doc = "A list of labels to add to a crate's `cargo_build_script::proc_macro_deps` attribute.", + ), + "build_script_rundir": attr.string( + doc = "An override for the build script's rundir attribute.", + ), + "build_script_rustc_env": attr.string_dict( + doc = "Additional environment variables to set on a crate's `cargo_build_script::env` attribute.", + ), + "build_script_toolchains": attr.label_list( + doc = "A list of labels to set on a crates's `cargo_build_script::toolchains` attribute.", + ), + "build_script_tools": _relative_label_list( + doc = "A list of labels to add to a crate's `cargo_build_script::tools` attribute.", + ), + "compile_data": _relative_label_list( + doc = "A list of labels to add to a crate's `rust_library::compile_data` attribute.", + ), + "compile_data_glob": attr.string_list( + doc = "A list of glob patterns to add to a crate's `rust_library::compile_data` attribute.", + ), + "crate": attr.string( + doc = "The name of the crate the annotation is applied to", + mandatory = True, + ), + "crate_features": attr.string_list( + doc = "A list of strings to add to a crate's `rust_library::crate_features` attribute.", + ), + "data": _relative_label_list( + doc = "A list of labels to add to a crate's `rust_library::data` attribute.", + ), + "data_glob": attr.string_list( + doc = "A list of glob patterns to add to a crate's `rust_library::data` attribute.", + ), + "deps": _relative_label_list( + doc = "A list of labels to add to a crate's `rust_library::deps` attribute.", + ), + "disable_pipelining": attr.bool( + doc = "If True, disables pipelining for library targets for this crate.", + ), + "extra_aliased_targets": attr.string_dict( + doc = "A list of targets to add to the generated aliases in the root crate_universe repository.", + ), + "gen_all_binaries": attr.bool( + doc = "If true, generates `rust_binary` targets for all of the crates bins", + ), + "gen_binaries": attr.string_list( + doc = "As a list, the subset of the crate's bins that should get `rust_binary` targets produced.", + ), + "gen_build_script": attr.string( + 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'.", + values = _OPT_BOOL_VALUES.keys(), + default = "auto", + ), + "override_target_bin": attr.label( + 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.", + ), + "override_target_build_script": attr.label( + 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.", + ), + "override_target_lib": attr.label( + 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.", + ), + "override_target_proc_macro": attr.label( + 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.", + ), + "patch_args": attr.string_list( + 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)", + ), + "patch_tool": attr.string( + 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)", + ), + "patches": attr.label_list( + 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)", + ), + "proc_macro_deps": _relative_label_list( + doc = "A list of labels to add to a crate's `rust_library::proc_macro_deps` attribute.", + ), + "repositories": attr.string_list( + doc = "A list of repository names specified from `crate.from_cargo(name=...)` that this annotation is applied to. Defaults to all repositories.", + default = [], + ), + "rustc_env": attr.string_dict( + doc = "Additional variables to set on a crate's `rust_library::rustc_env` attribute.", + ), + "rustc_env_files": _relative_label_list( + doc = "A list of labels to set on a crate's `rust_library::rustc_env_files` attribute.", + ), + "rustc_flags": attr.string_list( + doc = "A list of strings to set on a crate's `rust_library::rustc_flags` attribute.", + ), + "shallow_since": attr.string( + doc = "An optional timestamp used for crates originating from a git repository instead of a crate registry. This flag optimizes fetching the source code.", + ), + "version": attr.string( + doc = "The versions of the crate the annotation is applied to. Defaults to all versions.", + default = "*", + ), + }, +) + +_from_specs = tag_class( + doc = "Generates a repo @crates from the defined `spec` tags", + attrs = { + "cargo_config": CRATES_VENDOR_ATTRS["cargo_config"], + "generate_binaries": CRATES_VENDOR_ATTRS["generate_binaries"], + "generate_build_scripts": CRATES_VENDOR_ATTRS["generate_build_scripts"], + "name": attr.string( + doc = "The name of the repo to generate", + default = "crates", + ), + "splicing_config": CRATES_VENDOR_ATTRS["splicing_config"], + "supported_platform_triples": CRATES_VENDOR_ATTRS["supported_platform_triples"], + }, +) + +# This should be kept in sync with crate_universe/private/crate.bzl. +_spec = tag_class( + attrs = { + "artifact": attr.string( + doc = "Set to 'bin' to pull in a binary crate as an artifact dependency. Requires a nightly Cargo.", + ), + "branch": attr.string( + 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.", + ), + "default_features": attr.bool( + doc = "Maps to the `default-features` flag.", + default = True, + ), + "features": attr.string_list( + doc = "A list of features to use for the crate.", + ), + "git": attr.string( + doc = "The Git url to use for the crate. Cannot be used with `version`.", + ), + "lib": attr.bool( + 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.", + ), + "package": attr.string( + doc = "The explicit name of the package.", + mandatory = True, + ), + "rev": attr.string( + doc = "The git revision of the remote crate. Tied with the `git` param. Only one of branch, tag or rev may be specified.", + ), + "tag": attr.string( + 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.", + ), + "version": attr.string( + doc = "The exact version of the crate. Cannot be used with `git`.", + ), + }, +) + +_conditional_crate_args = { + "arch_dependent": True, + "os_dependent": True, +} if bazel_features.external_deps.module_extension_has_os_arch_dependent else {} + +crate = module_extension( + doc = "TODO", + implementation = _crate_impl, + tag_classes = { + "annotation": _annotation, + "from_cargo": _from_cargo, + "from_specs": _from_specs, + "spec": _spec, + }, + **_conditional_crate_args +) diff --git a/crate_universe/private/internal_extensions.bzl b/crate_universe/private/internal_extensions.bzl new file mode 100644 index 0000000000..44e4af0826 --- /dev/null +++ b/crate_universe/private/internal_extensions.bzl @@ -0,0 +1,54 @@ +"""Bzlmod module extensions that are only used internally""" + +load("//crate_universe:deps_bootstrap.bzl", "cargo_bazel_bootstrap") +load("//crate_universe:repositories.bzl", "crate_universe_dependencies") +load("//crate_universe/tools/cross_installer:cross_installer_deps.bzl", "cross_installer_deps") + +def _internal_deps_impl(module_ctx): + direct_deps = [] + + direct_deps.extend(crate_universe_dependencies()) + direct_deps.extend(cargo_bazel_bootstrap( + rust_toolchain_cargo_template = "@rust_host_tools//:bin/{tool}", + rust_toolchain_rustc_template = "@rust_host_tools//:bin/{tool}", + )) + + # is_dev_dep is ignored here. It's not relevant for internal_deps, as dev + # dependencies are only relevant for module extensions that can be used + # by other MODULES. + return module_ctx.extension_metadata( + root_module_direct_deps = [repo.repo for repo in direct_deps], + root_module_direct_dev_deps = [], + ) + +# This is named a single character to reduce the size of path names when running build scripts, to reduce the chance +# of hitting the 260 character windows path name limit. +# TODO: https://github.com/bazelbuild/rules_rust/issues/1120 +i = module_extension( + doc = "Dependencies for crate_universe.", + implementation = _internal_deps_impl, +) + +def _internal_dev_deps_impl(module_ctx): + direct_deps = [] + + direct_deps.extend(cross_installer_deps( + rust_toolchain_cargo_template = "@rust_host_tools//:bin/{tool}", + rust_toolchain_rustc_template = "@rust_host_tools//:bin/{tool}", + )) + + # is_dev_dep is ignored here. It's not relevant for internal_deps, as dev + # dependencies are only relevant for module extensions that can be used + # by other MODULES. + return module_ctx.extension_metadata( + root_module_direct_deps = [], + root_module_direct_dev_deps = [repo.repo for repo in direct_deps], + ) + +# This is named a single character to reduce the size of path names when running build scripts, to reduce the chance +# of hitting the 260 character windows path name limit. +# TODO: https://github.com/bazelbuild/rules_rust/issues/1120 +i_dev = module_extension( + doc = "Development dependencies for crate_universe.", + implementation = _internal_dev_deps_impl, +) diff --git a/crate_universe/private/module_extensions/BUILD.bazel b/crate_universe/private/module_extensions/BUILD.bazel deleted file mode 100644 index e4e24878d7..0000000000 --- a/crate_universe/private/module_extensions/BUILD.bazel +++ /dev/null @@ -1,13 +0,0 @@ -load("@bazel_skylib//:bzl_library.bzl", "bzl_library") - -filegroup( - name = "bzl_srcs", - srcs = glob(["*.bzl"]), - visibility = ["//visibility:public"], -) - -bzl_library( - name = "bzl_lib", - srcs = [":bzl_srcs"], - visibility = ["//visibility:public"], -) diff --git a/crate_universe/private/module_extensions/cargo_bazel_bootstrap.bzl b/crate_universe/private/module_extensions/cargo_bazel_bootstrap.bzl deleted file mode 100644 index 61ac5e4812..0000000000 --- a/crate_universe/private/module_extensions/cargo_bazel_bootstrap.bzl +++ /dev/null @@ -1,69 +0,0 @@ -"""Module extension for bootstrapping cargo-bazel.""" - -load("//crate_universe:deps_bootstrap.bzl", _cargo_bazel_bootstrap_repo_rule = "cargo_bazel_bootstrap") -load("//rust/platform:triple.bzl", "get_host_triple") -load("//rust/platform:triple_mappings.bzl", "system_to_binary_ext") - -def _cargo_bazel_bootstrap_impl(_): - _cargo_bazel_bootstrap_repo_rule( - rust_toolchain_cargo_template = "@rust_host_tools//:bin/{tool}", - rust_toolchain_rustc_template = "@rust_host_tools//:bin/{tool}", - ) - -cargo_bazel_bootstrap = module_extension( - implementation = _cargo_bazel_bootstrap_impl, - doc = """Module extension to generate the cargo_bazel binary.""", -) - -def get_host_cargo_rustc(module_ctx): - """A helper function to get the path to the host cargo and rustc binaries. - - Args: - module_ctx: The module extension's context. - Returns: - A tuple of path to cargo, path to rustc. - - """ - host_triple = get_host_triple(module_ctx) - binary_ext = system_to_binary_ext(host_triple.system) - - cargo_path = str(module_ctx.path(Label("@rust_host_tools//:bin/cargo{}".format(binary_ext)))) - rustc_path = str(module_ctx.path(Label("@rust_host_tools//:bin/rustc{}".format(binary_ext)))) - return cargo_path, rustc_path - -def get_cargo_bazel_runner(module_ctx, cargo_bazel): - """A helper function to allow executing cargo_bazel in module extensions. - - Args: - module_ctx: The module extension's context. - cargo_bazel: Path The path to a `cargo-bazel` binary - Returns: - A function that can be called to execute cargo_bazel. - """ - cargo_path, rustc_path = get_host_cargo_rustc(module_ctx) - - # Placing this as a nested function allows users to call this right at the - # start of a module extension, thus triggering any restarts as early as - # possible (since module_ctx.path triggers restarts). - def run(args, env = {}, timeout = 600): - final_args = [cargo_bazel] - final_args.extend(args) - final_args.extend([ - "--cargo", - cargo_path, - "--rustc", - rustc_path, - ]) - result = module_ctx.execute( - final_args, - environment = dict(CARGO = cargo_path, RUSTC = rustc_path, **env), - timeout = timeout, - ) - if result.return_code != 0: - if result.stdout: - print("Stdout:", result.stdout) # buildifier: disable=print - pretty_args = " ".join([str(arg) for arg in final_args]) - fail("%s returned with exit code %d:\n%s" % (pretty_args, result.return_code, result.stderr)) - return result - - return run diff --git a/crate_universe/tests/cargo_integration_test.rs b/crate_universe/tests/cargo_integration_test.rs index c2103850ee..ac45952734 100644 --- a/crate_universe/tests/cargo_integration_test.rs +++ b/crate_universe/tests/cargo_integration_test.rs @@ -400,8 +400,6 @@ fn resolver_2_deps() { return; } - cargo_bazel::cli::init_logging("Splice", true); - let r = runfiles::Runfiles::create().unwrap(); let metadata = run( "resolver_2_deps_test", diff --git a/crate_universe/tools/cross_installer/cross_installer_deps.bzl b/crate_universe/tools/cross_installer/cross_installer_deps.bzl index ea049440fa..1a7ee076d3 100644 --- a/crate_universe/tools/cross_installer/cross_installer_deps.bzl +++ b/crate_universe/tools/cross_installer/cross_installer_deps.bzl @@ -7,8 +7,18 @@ load("//cargo:defs.bzl", "cargo_bootstrap_repository") # buildifier: disable=bzl-visibility load("//rust/private:common.bzl", "DEFAULT_RUST_VERSION") -def cross_installer_deps(): - """Define cross repositories used for compiling cargo-bazel for various platforms""" +def cross_installer_deps(**kwargs): + """Define cross repositories used for compiling cargo-bazel for various platforms. + + Args: + **kwargs: kwargs to pass through to cargo_bootstrap_repository. + + Returns: + list[struct(repo=str, is_dev_dep=bool)]: A list of the repositories + defined by this macro. + """ + direct_deps = [] + maybe( http_archive, name = "cross_rs", @@ -19,6 +29,11 @@ def cross_installer_deps(): build_file_content = """exports_files(["Cargo.toml", "Cargo.lock"], visibility = ["//visibility:public"])""", ) + direct_deps.append(struct( + repo = "cross_rs", + is_dev_dep = True, + )) + version = DEFAULT_RUST_VERSION if "-" in version: version = "nightly/{}".format(version) @@ -30,4 +45,12 @@ def cross_installer_deps(): cargo_toml = "@cross_rs//:Cargo.toml", cargo_lockfile = "@cross_rs//:Cargo.lock", version = version, + **kwargs ) + + direct_deps.append(struct( + repo = "cross_rs_host_bin", + is_dev_dep = True, + )) + + return direct_deps diff --git a/docs/src/crate_universe_bzlmod.md b/docs/src/crate_universe_bzlmod.md index 69ba409916..85afe0901b 100644 --- a/docs/src/crate_universe_bzlmod.md +++ b/docs/src/crate_universe_bzlmod.md @@ -36,7 +36,7 @@ You find the latest version on the [release page](https://github.com/bazelbuild/ After adding `rules_rust` in your MODULE.bazel, set the following to begin using `crate_universe`: ```starlark -crate = use_extension("@rules_rust//crate_universe:extension.bzl", "crate") +crate = use_extension("@rules_rust//crate_universe:extensions.bzl", "crate") // # ... Dependencies use_repo(crate, "crates") ``` @@ -56,7 +56,7 @@ The crates_repository rule can ingest a root Cargo.toml file and generate Bazel You find a complete example in the in the [example folder](../examples/bzlmod/all_crate_deps). ```starlark -crate = use_extension("@rules_rust//crate_universe:extension.bzl", "crate") +crate = use_extension("@rules_rust//crate_universe:extensions.bzl", "crate") crate.from_cargo( name = "crates", @@ -144,7 +144,7 @@ crates_repository supports this through the packages attribute, as shown below. ```starlark -crate = use_extension("@rules_rust//crate_universe:extension.bzl", "crate") +crate = use_extension("@rules_rust//crate_universe:extensions.bzl", "crate") crate.spec(package = "serde", features = ["derive"], version = "1.0") crate.spec(package = "serde_json", version = "1.0") @@ -219,7 +219,7 @@ register_toolchains("@rust_toolchains//:all") ############################################################################### # R U S T C R A T E S ############################################################################### -crate = use_extension("@rules_rust//crate_universe:extension.bzl", "crate") +crate = use_extension("@rules_rust//crate_universe:extensions.bzl", "crate") ``` Note, it is important to load the crate_universe rules otherwise you will get an error @@ -450,5 +450,3 @@ Generates a repo @crates from the defined `spec` tags | rev | The git revision of the remote crate. Tied with the `git` param. Only one of branch, tag or rev may be specified. | String | optional | `""` | | tag | 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. | String | optional | `""` | | version | The exact version of the crate. Cannot be used with `git`. | String | optional | `""` | - - diff --git a/examples/bazel_env/MODULE.bazel b/examples/bazel_env/MODULE.bazel index e42e2637d1..cbb0b0c814 100644 --- a/examples/bazel_env/MODULE.bazel +++ b/examples/bazel_env/MODULE.bazel @@ -28,7 +28,7 @@ use_repo( register_toolchains("@rust_toolchains//:all") crate = use_extension( - "@rules_rust//crate_universe:extension.bzl", + "@rules_rust//crate_universe:extensions.bzl", "crate", ) crate.from_cargo( diff --git a/examples/bzlmod/all_crate_deps/MODULE.bazel b/examples/bzlmod/all_crate_deps/MODULE.bazel index 10033b1aa6..b5684b6605 100644 --- a/examples/bzlmod/all_crate_deps/MODULE.bazel +++ b/examples/bzlmod/all_crate_deps/MODULE.bazel @@ -29,7 +29,7 @@ use_repo( register_toolchains("@rust_toolchains//:all") crate = use_extension( - "@rules_rust//crate_universe:extension.bzl", + "@rules_rust//crate_universe:extensions.bzl", "crate", ) crate.from_cargo( diff --git a/examples/bzlmod/all_deps_vendor/MODULE.bazel b/examples/bzlmod/all_deps_vendor/MODULE.bazel index 654be4e41c..991b21f3f3 100644 --- a/examples/bzlmod/all_deps_vendor/MODULE.bazel +++ b/examples/bzlmod/all_deps_vendor/MODULE.bazel @@ -37,4 +37,4 @@ register_toolchains("@rust_toolchains//:all") ############################################################################### # R U S T C R A T E S ############################################################################### -use_extension("@rules_rust//crate_universe:extension.bzl", "crate") +use_extension("@rules_rust//crate_universe:extensions.bzl", "crate") diff --git a/examples/bzlmod/all_deps_vendor/README.md b/examples/bzlmod/all_deps_vendor/README.md index 465c8dc5ea..8cb40b6c70 100644 --- a/examples/bzlmod/all_deps_vendor/README.md +++ b/examples/bzlmod/all_deps_vendor/README.md @@ -47,16 +47,16 @@ register_toolchains("@rust_toolchains//:all") ############################################################################### # R U S T C R A T E S ############################################################################### -crate = use_extension("@rules_rust//crate_universe:extension.bzl", "crate") +crate = use_extension("@rules_rust//crate_universe:extensions.bzl", "crate") ``` Note, it is important to load the crate_universe rules otherwise you will get an error as the rule set is needed in the vendored target. -Assuming you have a package called `basic` in which you want to vendor dependencies, -then you create a folder `basic/3rdparty`. The folder name can be arbitrary, -but by convention, its either thirdparty or 3rdparty to indicate vendored dependencies. -In the 3rdparty folder, you add a target crates_vendor to declare your dependencies to vendor. In the example, we vendor a specific version of bzip2. +Assuming you have a package called `basic` in which you want to vendor dependencies, +then you create a folder `basic/3rdparty`. The folder name can be arbitrary, +but by convention, its either thirdparty or 3rdparty to indicate vendored dependencies. +In the 3rdparty folder, you add a target crates_vendor to declare your dependencies to vendor. In the example, we vendor a specific version of bzip2. ```starlark load("@rules_rust//crate_universe:defs.bzl", "crate", "crates_vendor") @@ -82,16 +82,16 @@ crates_vendor( ``` Next, you have to run `Cargo build` to generate a Cargo.lock file with all resolved dependencies. -Then, you rename Cargo.lock to Cargo.Bazel.lock and place it inside the `basic/3rdparty` folder. +Then, you rename Cargo.lock to Cargo.Bazel.lock and place it inside the `basic/3rdparty` folder. At this point, you have the following folder and files: ``` basic ├── 3rdparty - │ ├── BUILD.bazel - │ ├── Cargo.Bazel.lock -``` + │ ├── BUILD.bazel + │ ├── Cargo.Bazel.lock +``` Now you can run the `crates_vendor` target: @@ -102,14 +102,14 @@ This generates a crate folders with all configurations for the vendored dependen ``` basic ├── 3rdparty - │ ├── cratea - │ ├── BUILD.bazel - │ ├── Cargo.Bazel.lock -``` + │ ├── cratea + │ ├── BUILD.bazel + │ ├── Cargo.Bazel.lock +``` ## Usage -Suppose you have an application in `basic/src` that is defined in `basic/BUILD.bazel` and +Suppose you have an application in `basic/src` that is defined in `basic/BUILD.bazel` and that depends on a vendored dependency. You find a list of all available vendored dependencies in the BUILD file of the generated folder: `basic/3rdparty/crates/BUILD.bazel` You declare a vendored dependency in you target as following: @@ -128,7 +128,7 @@ rust_binary( ``` Note, the vendored dependency is not yet accessible. -Before you can build, you have to define how to load the vendored dependencies. For that, +Before you can build, you have to define how to load the vendored dependencies. For that, you first create a file `sys_deps.bzl` and add the following content: ```starlark @@ -175,4 +175,4 @@ bazel_dep(name = "bazel_skylib", version = "1.7.1") # .... ``` -Your build will complete once skylib loads. \ No newline at end of file +Your build will complete once skylib loads. diff --git a/examples/bzlmod/cross_compile/MODULE.bazel b/examples/bzlmod/cross_compile/MODULE.bazel index bca890e706..4e1962cc8d 100644 --- a/examples/bzlmod/cross_compile/MODULE.bazel +++ b/examples/bzlmod/cross_compile/MODULE.bazel @@ -98,7 +98,7 @@ register_toolchains("@rust_toolchains//:all") ############################################################################### # Rust Dependencies # ############################################################################### -crate = use_extension("@rules_rust//crate_universe:extension.bzl", "crate") +crate = use_extension("@rules_rust//crate_universe:extensions.bzl", "crate") crate.spec( package = "mimalloc", version = "0.1.43", diff --git a/examples/bzlmod/hello_world/MODULE.bazel b/examples/bzlmod/hello_world/MODULE.bazel index da42241fa4..201443d4b1 100644 --- a/examples/bzlmod/hello_world/MODULE.bazel +++ b/examples/bzlmod/hello_world/MODULE.bazel @@ -23,7 +23,7 @@ local_path_override( # Option 1: Fully transient (Cargo.toml / Cargo.lock as source of truth). crate = use_extension( - "@rules_rust//crate_universe:extension.bzl", + "@rules_rust//crate_universe:extensions.bzl", "crate", ) crate.from_cargo( diff --git a/examples/bzlmod/hello_world_no_cargo/MODULE.bazel b/examples/bzlmod/hello_world_no_cargo/MODULE.bazel index f0fea0d599..3aa3127940 100644 --- a/examples/bzlmod/hello_world_no_cargo/MODULE.bazel +++ b/examples/bzlmod/hello_world_no_cargo/MODULE.bazel @@ -17,7 +17,7 @@ use_repo(rust, "rust_toolchains") register_toolchains("@rust_toolchains//:all") -crate = use_extension("@rules_rust//crate_universe:extension.bzl", "crate") +crate = use_extension("@rules_rust//crate_universe:extensions.bzl", "crate") crate.spec( package = "anyhow", version = "1.0.77", diff --git a/examples/bzlmod/override_target/MODULE.bazel b/examples/bzlmod/override_target/MODULE.bazel index c8f9d79d66..97e3043f7f 100644 --- a/examples/bzlmod/override_target/MODULE.bazel +++ b/examples/bzlmod/override_target/MODULE.bazel @@ -29,7 +29,7 @@ use_repo( register_toolchains("@rust_toolchains//:all") crate = use_extension( - "@rules_rust//crate_universe:extension.bzl", + "@rules_rust//crate_universe:extensions.bzl", "crate", ) crate.from_cargo( diff --git a/examples/bzlmod/proto/MODULE.bazel b/examples/bzlmod/proto/MODULE.bazel index 46e05d6844..729342c395 100644 --- a/examples/bzlmod/proto/MODULE.bazel +++ b/examples/bzlmod/proto/MODULE.bazel @@ -80,7 +80,7 @@ register_toolchains("@//build/prost_toolchain") ############################################################################### # R U S T C R A T E S ############################################################################### -crate = use_extension("@rules_rust//crate_universe:extension.bzl", "crate") +crate = use_extension("@rules_rust//crate_universe:extensions.bzl", "crate") # # protobuf / gRPC dependencies diff --git a/examples/bzlmod/proto/README.md b/examples/bzlmod/proto/README.md index 38b0dbe9c7..e2b61a1bc2 100644 --- a/examples/bzlmod/proto/README.md +++ b/examples/bzlmod/proto/README.md @@ -81,7 +81,7 @@ register_toolchains("@rules_rust_prost//:default_prost_toolchain") # 3 Register proto / prost / tonic crates ############################################################################### -crate = use_extension("@rules_rust//crate_universe:extension.bzl", "crate") +crate = use_extension("@rules_rust//crate_universe:extensions.bzl", "crate") # protobufs / gRPC crate.spec( diff --git a/examples/bzlmod/proto_with_toolchain/MODULE.bazel b/examples/bzlmod/proto_with_toolchain/MODULE.bazel index 6a77343da4..a427a8d82d 100644 --- a/examples/bzlmod/proto_with_toolchain/MODULE.bazel +++ b/examples/bzlmod/proto_with_toolchain/MODULE.bazel @@ -80,7 +80,7 @@ register_toolchains("@//build/prost_toolchain") ############################################################################### # R U S T C R A T E S ############################################################################### -crate = use_extension("@rules_rust//crate_universe:extension.bzl", "crate") +crate = use_extension("@rules_rust//crate_universe:extensions.bzl", "crate") # # protobuf / gRPC dependencies diff --git a/examples/bzlmod/proto_with_toolchain/README.md b/examples/bzlmod/proto_with_toolchain/README.md index 38b0dbe9c7..e2b61a1bc2 100644 --- a/examples/bzlmod/proto_with_toolchain/README.md +++ b/examples/bzlmod/proto_with_toolchain/README.md @@ -81,7 +81,7 @@ register_toolchains("@rules_rust_prost//:default_prost_toolchain") # 3 Register proto / prost / tonic crates ############################################################################### -crate = use_extension("@rules_rust//crate_universe:extension.bzl", "crate") +crate = use_extension("@rules_rust//crate_universe:extensions.bzl", "crate") # protobufs / gRPC crate.spec( diff --git a/rust/extensions.bzl b/rust/extensions.bzl index 46d31fd8f9..5edf19dc4a 100644 --- a/rust/extensions.bzl +++ b/rust/extensions.bzl @@ -1,4 +1,4 @@ -"Module extensions for using rules_rust with bzlmod" +"""Module extensions for using rules_rust with bzlmod""" load("@bazel_features//:features.bzl", "bazel_features") load("//rust:defs.bzl", "rust_common") @@ -38,7 +38,7 @@ def _empty_repository_impl(repository_ctx): repository_ctx.file("BUILD.bazel", "") _empty_repository = repository_rule( - doc = ("Declare an empty repository."), + doc = "Declare an empty repository.", implementation = _empty_repository_impl, ) @@ -82,52 +82,61 @@ def _rust_impl(module_ctx): metadata_kwargs["reproducible"] = True return module_ctx.extension_metadata(**metadata_kwargs) -_COMMON_TAG_KWARGS = dict( - allocator_library = attr.string( +_COMMON_TAG_KWARGS = { + "allocator_library": attr.string( doc = "Target that provides allocator functions when rust_library targets are embedded in a cc_binary.", default = "@rules_rust//ffi/cc/allocator_library", ), - dev_components = attr.bool( + "dev_components": attr.bool( doc = "Whether to download the rustc-dev components (defaults to False). Requires version to be \"nightly\".", default = False, ), - edition = attr.string( + "edition": attr.string( doc = ( "The rust edition to be used by default (2015, 2018, or 2021). " + "If absent, every rule is required to specify its `edition` attribute." ), ), - rustfmt_version = attr.string( + "rustfmt_version": attr.string( doc = "The version of the tool among \"nightly\", \"beta\", or an exact version.", default = DEFAULT_NIGHTLY_VERSION, ), - sha256s = attr.string_dict( + "sha256s": attr.string_dict( doc = "A dict associating tool subdirectories to sha256 hashes. See [rust_repositories](#rust_repositories) for more details.", ), - urls = attr.string_list( + "urls": attr.string_list( doc = "A list of mirror urls containing the tools from the Rust-lang static file server. These must contain the '{}' used to substitute the tool being fetched (using .format).", default = DEFAULT_STATIC_RUST_URL_TEMPLATES, ), -) +} _RUST_TOOLCHAIN_TAG = tag_class( - attrs = dict( - extra_target_triples = attr.string_list( - default = DEFAULT_EXTRA_TARGET_TRIPLES, + attrs = { + "aliases": attr.string_dict( + doc = ( + "Map of full toolchain repository name to an alias. If any repository is created by this " + + "extension matches a key in this dictionary, the name of the created repository will be " + + "remapped to the value instead. This may be required to work around path length limits " + + "on Windows." + ), + default = {}, ), - extra_exec_rustc_flags = attr.string_list( + "extra_exec_rustc_flags": attr.string_list( doc = "Extra flags to pass to rustc in exec configuration", ), - extra_rustc_flags = attr.string_list( + "extra_rustc_flags": attr.string_list( doc = "Extra flags to pass to rustc in non-exec configuration", ), - extra_rustc_flags_triples = attr.string_list_dict( + "extra_rustc_flags_triples": attr.string_list_dict( doc = "Extra flags to pass to rustc in non-exec configuration. Key is the triple, value is the flag.", ), - rust_analyzer_version = attr.string( + "extra_target_triples": attr.string_list( + default = DEFAULT_EXTRA_TARGET_TRIPLES, + ), + "rust_analyzer_version": attr.string( doc = "The version of Rustc to pair with rust-analyzer.", ), - versions = attr.string_list( + "versions": attr.string_list( doc = ( "A list of toolchain versions to download. This parameter only accepts one version " + "per channel. E.g. `[\"1.65.0\", \"nightly/2022-11-02\", \"beta/2020-12-30\"]`. " + @@ -135,30 +144,20 @@ _RUST_TOOLCHAIN_TAG = tag_class( ), default = _RUST_TOOLCHAIN_VERSIONS, ), - aliases = attr.string_dict( - doc = ( - "Map of full toolchain repository name to an alias. If any repository is created by this " + - "extension matches a key in this dictionary, the name of the created repository will be " + - "remapped to the value instead. This may be required to work around path length limits " + - "on Windows." - ), - default = {}, - ), - **_COMMON_TAG_KWARGS - ), + } | _COMMON_TAG_KWARGS, ) _RUST_HOST_TOOLS_TAG = tag_class( - attrs = dict( - version = attr.string( + attrs = { + "version": attr.string( default = rust_common.default_version, doc = "The version of Rust to use for tools executed on the Bazel host.", ), - **_COMMON_TAG_KWARGS - ), + } | _COMMON_TAG_KWARGS, ) rust = module_extension( + doc = "Rust toolchain extension.", implementation = _rust_impl, tag_classes = { "toolchain": _RUST_TOOLCHAIN_TAG, @@ -208,6 +207,7 @@ _conditional_rust_host_tools_args = { } if bazel_features.external_deps.module_extension_has_os_arch_dependent else {} rust_host_tools = module_extension( + doc = "An extension which exposes Rust tools compatible with the current host platform.", implementation = _rust_host_tools_impl, tag_classes = { "host_tools": _RUST_HOST_TOOLS_TAG, diff --git a/rust/private/extensions.bzl b/rust/private/internal_extensions.bzl similarity index 91% rename from rust/private/extensions.bzl rename to rust/private/internal_extensions.bzl index 08401875f0..53ec3d646a 100644 --- a/rust/private/extensions.bzl +++ b/rust/private/internal_extensions.bzl @@ -1,7 +1,6 @@ """Bzlmod module extensions that are only used internally""" load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") -load("//crate_universe:repositories.bzl", "crate_universe_dependencies") load("//rust/private:repository_utils.bzl", "TINYJSON_KWARGS") load("//tools/rust_analyzer:deps.bzl", "rust_analyzer_dependencies") @@ -14,7 +13,6 @@ def _internal_deps_impl(module_ctx): direct_deps = [struct(repo = "rules_rust_tinyjson", is_dev_dep = False)] http_archive(**TINYJSON_KWARGS) - direct_deps.extend(crate_universe_dependencies()) direct_deps.extend(rust_analyzer_dependencies()) # is_dev_dep is ignored here. It's not relevant for internal_deps, as dev diff --git a/rust/private/rustc.bzl b/rust/private/rustc.bzl index fbe2abf176..fc8d34dbde 100644 --- a/rust/private/rustc.bzl +++ b/rust/private/rustc.bzl @@ -2128,7 +2128,12 @@ def _add_per_crate_rustc_flags(ctx, args, crate_info, per_crate_rustc_flags): fail("per_crate_rustc_flag '{}' does not follow the expected format: prefix_filter@flag".format(per_crate_rustc_flag)) label_string = str(ctx.label) - label = label_string[1:] if label_string.startswith("@//") else label_string + if label_string.startswith("@//"): + label = label_string[1:] + elif label_string.startswith("@@//"): + label = label_string[2:] + else: + label = label_string execution_path = crate_info.root.path if label.startswith(prefix_filter) or execution_path.startswith(prefix_filter): diff --git a/test/cargo_build_script/location_expansion/test.rs b/test/cargo_build_script/location_expansion/test.rs index 0f9d70918b..e981cb692d 100644 --- a/test/cargo_build_script/location_expansion/test.rs +++ b/test/cargo_build_script/location_expansion/test.rs @@ -8,9 +8,15 @@ pub fn test_data_rootpath() { #[test] pub fn test_data_rlocation() { - assert_eq!( - "rules_rust/test/cargo_build_script/location_expansion/target_data.txt", - env!("DATA_RLOCATIONPATH") + assert!( + [ + // workspace + "rules_rust/test/cargo_build_script/location_expansion/target_data.txt", + // bzlmod + "_main/test/cargo_build_script/location_expansion/target_data.txt", + ] + .contains(&env!("DATA_RLOCATIONPATH")), + concat!("Unexpected rlocationpath: ", env!("DATA_RLOCATIONPATH")) ); } @@ -24,9 +30,15 @@ pub fn test_tool_rootpath() { #[test] pub fn test_tool_rlocationpath() { - assert_eq!( - "rules_rust/test/cargo_build_script/location_expansion/exec_data.txt", - env!("TOOL_RLOCATIONPATH") + assert!( + [ + // workspace + "rules_rust/test/cargo_build_script/location_expansion/exec_data.txt", + // bzlmod + "_main/test/cargo_build_script/location_expansion/exec_data.txt", + ] + .contains(&env!("TOOL_RLOCATIONPATH")), + concat!("Unexpected rlocationpath: ", env!("TOOL_RLOCATIONPATH")) ); } diff --git a/test/deps.bzl b/test/deps.bzl index 6f6d1bbb96..abf8a97c56 100644 --- a/test/deps.bzl +++ b/test/deps.bzl @@ -24,9 +24,13 @@ rust_library( ) """ -def rules_rust_test_deps(): +def rules_rust_test_deps(is_bzlmod = False): """Load dependencies for rules_rust tests + Args: + is_bzlmod (bool): Whether or not the context from which this macro + is called is bzlmod. + Returns: list[struct(repo=str, is_dev_dep=bool)]: A list of the repositories defined by this macro. @@ -53,19 +57,26 @@ def rules_rust_test_deps(): target_json = Label("//test/unit/toolchain:toolchain-test-triple.json"), ) - maybe( - http_archive, - name = "rules_python", - sha256 = "778aaeab3e6cfd56d681c89f5c10d7ad6bf8d2f1a72de9de55b23081b2d31618", - strip_prefix = "rules_python-0.34.0", - url = "https://github.com/bazelbuild/rules_python/releases/download/0.34.0/rules_python-0.34.0.tar.gz", - ) + if not is_bzlmod: + maybe( + http_archive, + name = "rules_python", + sha256 = "690e0141724abb568267e003c7b6d9a54925df40c275a870a4d934161dc9dd53", + strip_prefix = "rules_python-0.40.0", + url = "https://github.com/bazelbuild/rules_python/releases/download/0.40.0/rules_python-0.40.0.tar.gz", + ) + + maybe( + http_archive, + name = "rules_testing", + sha256 = "28c2d174471b587bf0df1fd3a10313f22c8906caf4050f8b46ec4648a79f90c3", + strip_prefix = "rules_testing-0.7.0", + url = "https://github.com/bazelbuild/rules_testing/releases/download/v0.7.0/rules_testing-v0.7.0.tar.gz", + ) direct_deps.extend([ struct(repo = "libc", is_dev_dep = True), struct(repo = "rules_rust_toolchain_test_target_json", is_dev_dep = True), - struct(repo = "com_google_googleapis", is_dev_dep = True), - struct(repo = "rules_python", is_dev_dep = True), ]) return direct_deps diff --git a/test/process_wrapper/BUILD.bazel b/test/process_wrapper/BUILD.bazel index dcce878303..296751afe5 100644 --- a/test/process_wrapper/BUILD.bazel +++ b/test/process_wrapper/BUILD.bazel @@ -164,5 +164,9 @@ rust_test( "//util/process_wrapper", ], edition = "2018", + rustc_env = { + "FAKE_RUSTC_RLOCATIONPATH": "$(rlocationpath :fake_rustc)", + "PROCESS_WRAPPER_RLOCATIONPATH": "$(rlocationpath //util/process_wrapper)", + }, deps = ["//tools/runfiles"], ) diff --git a/test/process_wrapper/process_wrapper_tester.bzl b/test/process_wrapper/process_wrapper_tester.bzl index 3f1268dbdc..87bd3dad22 100644 --- a/test/process_wrapper/process_wrapper_tester.bzl +++ b/test/process_wrapper/process_wrapper_tester.bzl @@ -12,12 +12,9 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""Process wrapper test. +"""Process wrapper test.""" -This rule unit tests the different process_wrapper functionality. -""" - -def _impl(ctx): +def _process_wrapper_tester_impl(ctx): args = ctx.actions.args() outputs = [] combined = ctx.attr.test_config == "combined" @@ -67,6 +64,7 @@ def _impl(ctx): env = {"CURRENT_DIR": "${pwd}/test_path"} ctx.actions.run( + mnemonic = "RustcProcessWrapperTester", executable = ctx.executable._process_wrapper, inputs = ctx.files.env_files + ctx.files.arg_files, outputs = outputs, @@ -78,12 +76,33 @@ def _impl(ctx): return [DefaultInfo(files = depset(outputs))] process_wrapper_tester = rule( - implementation = _impl, + implementation = _process_wrapper_tester_impl, + doc = "This rule unit tests the different process_wrapper functionality.", attrs = { - "arg_files": attr.label_list(), - "env_files": attr.label_list(), - "test_config": attr.string(mandatory = True), - "use_param_file": attr.bool(default = False), + "arg_files": attr.label_list( + doc = "Files containing newline delimited arguments.", + ), + "env_files": attr.label_list( + doc = "Files containing newline delimited environment key/value pairs.", + ), + "test_config": attr.string( + doc = "The desired test configuration.", + mandatory = True, + values = [ + "arg-files", + "basic", + "combined", + "copy-output", + "env-files", + "stderr", + "stdout", + "subst-pwd", + ], + ), + "use_param_file": attr.bool( + doc = "Whether or not to use a params file with the process wrapper.", + default = False, + ), "_process_wrapper": attr.label( default = Label("//util/process_wrapper"), executable = True, diff --git a/test/process_wrapper/rustc_quit_on_rmeta.rs b/test/process_wrapper/rustc_quit_on_rmeta.rs index 5ea3e2e9d2..b804ba1ebb 100644 --- a/test/process_wrapper/rustc_quit_on_rmeta.rs +++ b/test/process_wrapper/rustc_quit_on_rmeta.rs @@ -1,6 +1,5 @@ #[cfg(test)] mod test { - use std::path::PathBuf; use std::process::Command; use std::str; @@ -15,39 +14,10 @@ mod test { should_succeed: bool, ) -> String { let r = Runfiles::create().unwrap(); - let fake_rustc = runfiles::rlocation!( - r, - [ - "rules_rust", - "test", - "process_wrapper", - if cfg!(unix) { - "fake_rustc" - } else { - "fake_rustc.exe" - }, - ] - .iter() - .collect::() - ) - .unwrap(); + let fake_rustc = runfiles::rlocation!(r, env!("FAKE_RUSTC_RLOCATIONPATH")).unwrap(); - let process_wrapper = runfiles::rlocation!( - r, - [ - "rules_rust", - "util", - "process_wrapper", - if cfg!(unix) { - "process_wrapper" - } else { - "process_wrapper.exe" - }, - ] - .iter() - .collect::() - ) - .unwrap(); + let process_wrapper = + runfiles::rlocation!(r, env!("PROCESS_WRAPPER_RLOCATIONPATH")).unwrap(); let output = Command::new(process_wrapper) .args(process_wrapper_args) diff --git a/test/test_extensions.bzl b/test/test_extensions.bzl index 121a49c721..3c27569431 100644 --- a/test/test_extensions.bzl +++ b/test/test_extensions.bzl @@ -1,12 +1,23 @@ """Bzlmod test extensions""" +load("//test:deps.bzl", "rules_rust_test_deps") load("//test/3rdparty/crates:crates.bzl", test_crate_repositories = "crate_repositories") -load("//tests:test_deps.bzl", "helm_test_deps") -def _rust_test_impl(_ctx): - helm_test_deps() - test_crate_repositories() +def _rust_test_impl(module_ctx): + deps = [] + + deps.extend(rules_rust_test_deps()) + deps.extend(test_crate_repositories()) + + # is_dev_dep is ignored here. It's not relevant for internal_deps, as dev + # dependencies are only relevant for module extensions that can be used + # by other MODULES. + return module_ctx.extension_metadata( + root_module_direct_deps = [], + root_module_direct_dev_deps = [repo.repo for repo in deps], + ) rust_test = module_extension( + doc = "An extension for tests of rules_rust.", implementation = _rust_test_impl, ) diff --git a/test/unit/linkstamps/linkstamps_test.bzl b/test/unit/linkstamps/linkstamps_test.bzl index a0ae6fe519..d36f3e468a 100644 --- a/test/unit/linkstamps/linkstamps_test.bzl +++ b/test/unit/linkstamps/linkstamps_test.bzl @@ -8,6 +8,9 @@ load("//test/unit:common.bzl", "assert_action_mnemonic") def _is_running_on_linux(ctx): return ctx.target_platform_has_constraint(ctx.attr._linux[platform_common.ConstraintValueInfo]) +def _get_workspace_prefix(ctx): + return "" if ctx.workspace_name in ["rules_rust", "_main"] else "/external/rules_rust" + def _supports_linkstamps_test(ctx): env = analysistest.begin(ctx) tut = analysistest.target_under_test(env) @@ -21,7 +24,7 @@ def _supports_linkstamps_test(ctx): asserts.equals(env, linkstamp_out.basename, "linkstamp.o") tut_out = tut.files.to_list()[0] is_test = tut[rust_common.crate_info].is_test - workspace_prefix = "" if ctx.workspace_name == "rules_rust" else "/external/rules_rust" + workspace_prefix = _get_workspace_prefix(ctx) # Rust compilation outputs coming from a test are put in test-{hash} directory # which we need to remove in order to obtain the linkstamp file path. @@ -53,7 +56,9 @@ def _supports_linkstamps_test(ctx): supports_linkstamps_test = analysistest.make( _supports_linkstamps_test, attrs = { - "_linux": attr.label(default = Label("@platforms//os:linux")), + "_linux": attr.label( + default = Label("@platforms//os:linux"), + ), }, ) diff --git a/test/unit/native_deps/native_deps_test.bzl b/test/unit/native_deps/native_deps_test.bzl index aec46c359a..6ed7cb09df 100644 --- a/test/unit/native_deps/native_deps_test.bzl +++ b/test/unit/native_deps/native_deps_test.bzl @@ -129,6 +129,9 @@ def _extract_linker_args(argv): ) ] +def _get_workspace_prefix(ctx): + return "" if ctx.workspace_name in ["rules_rust", "_main"] else "external/rules_rust/" + def _bin_has_native_dep_and_alwayslink_test_impl(ctx): env = analysistest.begin(ctx) tut = analysistest.target_under_test(env) @@ -136,7 +139,7 @@ def _bin_has_native_dep_and_alwayslink_test_impl(ctx): toolchain = _get_toolchain(ctx) compilation_mode = ctx.var["COMPILATION_MODE"] - workspace_prefix = "" if ctx.workspace_name == "rules_rust" else "external/rules_rust/" + workspace_prefix = _get_workspace_prefix(ctx) link_args = _extract_linker_args(action.argv) if toolchain.target_os == "darwin": darwin_component = _get_darwin_component(link_args[-1]) @@ -190,7 +193,7 @@ def _cdylib_has_native_dep_and_alwayslink_test_impl(ctx): toolchain = _get_toolchain(ctx) compilation_mode = ctx.var["COMPILATION_MODE"] - workspace_prefix = "" if ctx.workspace_name == "rules_rust" else "external/rules_rust/" + workspace_prefix = _get_workspace_prefix(ctx) pic_suffix = _get_pic_suffix(ctx, compilation_mode) if toolchain.target_os == "darwin": darwin_component = _get_darwin_component(linker_args[-1])