From 8b548d2432d3958e69093c7a0eb5d5550e067347 Mon Sep 17 00:00:00 2001 From: Vinh Tran Date: Fri, 6 Oct 2023 17:18:35 -0400 Subject: [PATCH] Refactor CrateInfo construction (#2161) ## Description This PR addresses https://github.com/bazelbuild/rules_rust/issues/2013 to ensure rust-analyzer can access all the env vars the rust rules pass to `rustc` at compile time. This is a large refactoring in almost all the rules and aspects so I aim this PR to just fix `rust_library` rule. The follow-up PR will address the remaining rules and aspects. ## Summary * Create `create_crate_info_dict` function in `rust/private/utils.bzl` to create a mutable dict repsenting CrateInfo * Move `_determine_lib_name`, `get_edition`, `_transform_sources` functions to `rust/private/utils.bzl` to avoid cyclic dependency when `create_crate_info_dict` function use these * Introduce optional `create_crate_info_callback` attribute to `rustc_compile_action` to allow creating `CrateInfo` inside `rustc_compile_action`. This optional attribute allows scoping the refactoring in this PR to just `rust_library` rule. * Introduce optional `skip_expanding_rustc_env` attribute to `rustc_compile_action` to skip expanding `rustc_env` attr. This is useful for `clippy` aspect and `rust_test` rule because the `CrateInfo` provided from the depended `rust_library` already expands all the env vars before returning the provider downstream. ## Notes * `rustc_env_attr` is a temporary field in `CrateInfo` and needed by the rules and aspects not migrated to create `CrateInfo` in `rustc_compile_action` yet. It will be remove in the next PR after `CrateInfo.rustc_env` is always expanded. * `create_crate_info_callback` will be removed from `rustc_compile_action` after all `CrateInfo`s are created inside `rustc_compile_action`. --------- Co-authored-by: scentini --- docs/flatten.md | 3 +- docs/providers.md | 3 +- proto/prost/private/prost.bzl | 1 + proto/protobuf/proto.bzl | 1 + rust/private/clippy.bzl | 1 + rust/private/providers.bzl | 2 + rust/private/rust.bzl | 201 +++--------------- rust/private/rust_analyzer.bzl | 2 +- rust/private/rustc.bzl | 52 ++++- rust/private/rustdoc.bzl | 2 + rust/private/rustdoc_test.bzl | 1 + rust/private/utils.bzl | 189 ++++++++++++++++ .../with_modified_crate_name.bzl | 1 + test/unit/force_all_deps_direct/generator.bzl | 1 + test/unit/pipelined_compilation/wrap.bzl | 1 + 15 files changed, 279 insertions(+), 182 deletions(-) diff --git a/docs/flatten.md b/docs/flatten.md index 3041104307..330d743b6f 100644 --- a/docs/flatten.md +++ b/docs/flatten.md @@ -1450,7 +1450,7 @@ A toolchain for [rustfmt](https://rust-lang.github.io/rustfmt/)
 CrateInfo(aliases, compile_data, compile_data_targets, data, deps, edition, is_test, metadata, name,
           output, owner, proc_macro_deps, root, rustc_env, rustc_env_files, srcs, type,
-          wrapped_crate_type)
+          wrapped_crate_type, _rustc_env_attr)
 
A provider containing general Crate information. @@ -1478,6 +1478,7 @@ A provider containing general Crate information. | srcs | depset[File]: All source Files that are part of the crate. | | type | str: The type of this crate (see [rustc --crate-type](https://doc.rust-lang.org/rustc/command-line-arguments.html#--crate-type-a-list-of-types-of-crates-for-the-compiler-to-emit)). | | wrapped_crate_type | str, optional: The original crate type for targets generated using a previously defined crate (typically tests using the rust_test::crate attribute) | +| _rustc_env_attr | Dict[String, String]: Additional "key": "value" environment variables to set for rustc. | diff --git a/docs/providers.md b/docs/providers.md index c97ad9bce9..e553ffb949 100644 --- a/docs/providers.md +++ b/docs/providers.md @@ -12,7 +12,7 @@
 CrateInfo(aliases, compile_data, compile_data_targets, data, deps, edition, is_test, metadata, name,
           output, owner, proc_macro_deps, root, rustc_env, rustc_env_files, srcs, type,
-          wrapped_crate_type)
+          wrapped_crate_type, _rustc_env_attr)
 
A provider containing general Crate information. @@ -40,6 +40,7 @@ A provider containing general Crate information. | srcs | depset[File]: All source Files that are part of the crate. | | type | str: The type of this crate (see [rustc --crate-type](https://doc.rust-lang.org/rustc/command-line-arguments.html#--crate-type-a-list-of-types-of-crates-for-the-compiler-to-emit)). | | wrapped_crate_type | str, optional: The original crate type for targets generated using a previously defined crate (typically tests using the rust_test::crate attribute) | +| _rustc_env_attr | Dict[String, String]: Additional "key": "value" environment variables to set for rustc. | diff --git a/proto/prost/private/prost.bzl b/proto/prost/private/prost.bzl index 6cd3d5226f..b513b154ed 100644 --- a/proto/prost/private/prost.bzl +++ b/proto/prost/private/prost.bzl @@ -173,6 +173,7 @@ def _compile_rust(ctx, attr, crate_name, src, deps, edition): edition = edition, is_test = False, rustc_env = {}, + _rustc_env_attr = {}, compile_data = depset([]), compile_data_targets = depset([]), owner = ctx.label, diff --git a/proto/protobuf/proto.bzl b/proto/protobuf/proto.bzl index ca2df8a21f..2fcc1db50a 100644 --- a/proto/protobuf/proto.bzl +++ b/proto/protobuf/proto.bzl @@ -226,6 +226,7 @@ def _rust_proto_compile(protos, descriptor_sets, imports, crate_name, ctx, is_gr metadata = rust_metadata, edition = proto_toolchain.edition, rustc_env = {}, + _rustc_env_attr = {}, is_test = False, compile_data = depset([target.files for target in getattr(ctx.attr, "compile_data", [])]), compile_data_targets = depset(getattr(ctx.attr, "compile_data", [])), diff --git a/rust/private/clippy.bzl b/rust/private/clippy.bzl index 0f1f35ab82..d124c71f33 100644 --- a/rust/private/clippy.bzl +++ b/rust/private/clippy.bzl @@ -126,6 +126,7 @@ def _clippy_aspect_impl(target, ctx): build_env_files = build_env_files, build_flags_files = build_flags_files, emit = ["dep-info", "metadata"], + skip_expanding_rustc_env = True, ) if crate_info.is_test: diff --git a/rust/private/providers.bzl b/rust/private/providers.bzl index e4db9c9f9d..b2fced7442 100644 --- a/rust/private/providers.bzl +++ b/rust/private/providers.bzl @@ -41,6 +41,8 @@ CrateInfo = provider( "str, optional: The original crate type for targets generated using a previously defined " + "crate (typically tests using the `rust_test::crate` attribute)" ), + # TODO: Remove `_rustc_env_attr` after refactoring rust_test to only rely on rustc_env + "_rustc_env_attr": "Dict[String, String]: Additional `\"key\": \"value\"` environment variables to set for rustc.", }, ) diff --git a/rust/private/rust.bzl b/rust/private/rust.bzl index 014d8d0d52..d736fc6972 100644 --- a/rust/private/rust.bzl +++ b/rust/private/rust.bzl @@ -14,21 +14,22 @@ """Rust rule implementations""" -load("@bazel_skylib//lib:paths.bzl", "paths") load("//rust/private:common.bzl", "rust_common") load("//rust/private:providers.bzl", "BuildInfo") load("//rust/private:rustc.bzl", "rustc_compile_action") load( "//rust/private:utils.bzl", - "can_build_metadata", "compute_crate_name", "crate_root_src", + "create_crate_info_dict", "dedent", "determine_output_hash", "expand_dict_value_locations", "find_toolchain", + "get_edition", "get_import_macro_deps", "transform_deps", + "transform_sources", ) # TODO(marco): Separate each rule into its own file. @@ -66,129 +67,6 @@ def _assert_correct_dep_mapping(ctx): ), ) -def _determine_lib_name(name, crate_type, toolchain, lib_hash = None): - """See https://github.com/bazelbuild/rules_rust/issues/405 - - Args: - name (str): The name of the current target - crate_type (str): The `crate_type` - toolchain (rust_toolchain): The current `rust_toolchain` - lib_hash (str, optional): The hashed crate root path - - Returns: - str: A unique library name - """ - extension = None - prefix = "" - if crate_type in ("dylib", "cdylib", "proc-macro"): - extension = toolchain.dylib_ext - elif crate_type == "staticlib": - extension = toolchain.staticlib_ext - elif crate_type in ("lib", "rlib"): - # All platforms produce 'rlib' here - extension = ".rlib" - prefix = "lib" - elif crate_type == "bin": - fail("crate_type of 'bin' was detected in a rust_library. Please compile " + - "this crate as a rust_binary instead.") - - if not extension: - fail(("Unknown crate_type: {}. If this is a cargo-supported crate type, " + - "please file an issue!").format(crate_type)) - - prefix = "lib" - if toolchain.target_triple and toolchain.target_os == "windows" and crate_type not in ("lib", "rlib"): - prefix = "" - if toolchain.target_arch == "wasm32" and crate_type == "cdylib": - prefix = "" - - return "{prefix}{name}{lib_hash}{extension}".format( - prefix = prefix, - name = name, - lib_hash = "-" + lib_hash if lib_hash else "", - extension = extension, - ) - -def get_edition(attr, toolchain, label): - """Returns the Rust edition from either the current rule's attirbutes or the current `rust_toolchain` - - Args: - attr (struct): The current rule's attributes - toolchain (rust_toolchain): The `rust_toolchain` for the current target - label (Label): The label of the target being built - - Returns: - str: The target Rust edition - """ - if getattr(attr, "edition"): - return attr.edition - elif not toolchain.default_edition: - fail("Attribute `edition` is required for {}.".format(label)) - else: - return toolchain.default_edition - -def _symlink_for_non_generated_source(ctx, src_file, package_root): - """Creates and returns a symlink for non-generated source files. - - This rule uses the full path to the source files and the rule directory to compute - the relative paths. This is needed, instead of using `short_path`, because of non-generated - source files in external repositories possibly returning relative paths depending on the - current version of Bazel. - - Args: - ctx (struct): The current rule's context. - src_file (File): The source file. - package_root (File): The full path to the directory containing the current rule. - - Returns: - File: The created symlink if a non-generated file, or the file itself. - """ - - if src_file.is_source or src_file.root.path != ctx.bin_dir.path: - src_short_path = paths.relativize(src_file.path, src_file.root.path) - src_symlink = ctx.actions.declare_file(paths.relativize(src_short_path, package_root)) - ctx.actions.symlink( - output = src_symlink, - target_file = src_file, - progress_message = "Creating symlink to source file: {}".format(src_file.path), - ) - return src_symlink - else: - return src_file - -def _transform_sources(ctx, srcs, crate_root): - """Creates symlinks of the source files if needed. - - Rustc assumes that the source files are located next to the crate root. - In case of a mix between generated and non-generated source files, this - we violate this assumption, as part of the sources will be located under - bazel-out/... . In order to allow for targets that contain both generated - and non-generated source files, we generate symlinks for all non-generated - files. - - Args: - ctx (struct): The current rule's context. - srcs (List[File]): The sources listed in the `srcs` attribute - crate_root (File): The file specified in the `crate_root` attribute, - if it exists, otherwise None - - Returns: - Tuple(List[File], File): The transformed srcs and crate_root - """ - has_generated_sources = len([src for src in srcs if not src.is_source]) > 0 - - if not has_generated_sources: - return srcs, crate_root - - package_root = paths.dirname(paths.join(ctx.label.workspace_root, ctx.build_file_path)) - generated_sources = [_symlink_for_non_generated_source(ctx, src, package_root) for src in srcs if src != crate_root] - generated_root = crate_root - if crate_root: - generated_root = _symlink_for_non_generated_source(ctx, crate_root, package_root) - generated_sources.append(generated_root) - - return generated_sources, generated_root - def _rust_library_impl(ctx): """The implementation of the `rust_library` rule. @@ -265,7 +143,7 @@ def _rust_library_common(ctx, crate_type): crate_root = getattr(ctx.file, "crate_root", None) if not crate_root: crate_root = crate_root_src(ctx.attr.name, ctx.files.srcs, crate_type) - srcs, crate_root = _transform_sources(ctx, ctx.files.srcs, crate_root) + _, crate_root = transform_sources(ctx, ctx.files.srcs, crate_root) # Determine unique hash for this rlib. # Note that we don't include a hash for `cdylib` and `staticlib` since they are meant to be consumed externally @@ -277,49 +155,13 @@ def _rust_library_common(ctx, crate_type): else: output_hash = determine_output_hash(crate_root, ctx.label) - crate_name = compute_crate_name(ctx.workspace_name, ctx.label, toolchain, ctx.attr.crate_name) - rust_lib_name = _determine_lib_name( - crate_name, - crate_type, - toolchain, - output_hash, - ) - rust_lib = ctx.actions.declare_file(rust_lib_name) - - rust_metadata = None - if can_build_metadata(toolchain, ctx, crate_type) and not ctx.attr.disable_pipelining: - rust_metadata = ctx.actions.declare_file( - paths.replace_extension(rust_lib_name, ".rmeta"), - sibling = rust_lib, - ) - - deps = transform_deps(ctx.attr.deps) - proc_macro_deps = transform_deps(ctx.attr.proc_macro_deps + get_import_macro_deps(ctx)) - return rustc_compile_action( ctx = ctx, attr = ctx.attr, toolchain = toolchain, - crate_info = rust_common.create_crate_info( - name = crate_name, - type = crate_type, - root = crate_root, - srcs = depset(srcs), - deps = depset(deps), - proc_macro_deps = depset(proc_macro_deps), - aliases = ctx.attr.aliases, - output = rust_lib, - metadata = rust_metadata, - edition = get_edition(ctx.attr, toolchain, ctx.label), - rustc_env = ctx.attr.rustc_env, - rustc_env_files = ctx.files.rustc_env_files, - is_test = False, - data = depset(ctx.files.data), - compile_data = depset(ctx.files.compile_data), - compile_data_targets = depset(ctx.attr.compile_data), - owner = ctx.label, - ), output_hash = output_hash, + crate_type = crate_type, + create_crate_info_callback = create_crate_info_dict, ) def _rust_binary_impl(ctx): @@ -343,7 +185,7 @@ def _rust_binary_impl(ctx): crate_root = getattr(ctx.file, "crate_root", None) if not crate_root: crate_root = crate_root_src(ctx.attr.name, ctx.files.srcs, ctx.attr.crate_type) - srcs, crate_root = _transform_sources(ctx, ctx.files.srcs, crate_root) + srcs, crate_root = transform_sources(ctx, ctx.files.srcs, crate_root) return rustc_compile_action( ctx = ctx, @@ -359,6 +201,7 @@ def _rust_binary_impl(ctx): aliases = ctx.attr.aliases, output = output, edition = get_edition(ctx.attr, toolchain, ctx.label), + _rustc_env_attr = ctx.attr.rustc_env, rustc_env = ctx.attr.rustc_env, rustc_env_files = ctx.files.rustc_env_files, is_test = False, @@ -399,7 +242,7 @@ def _rust_test_impl(ctx): ), ) - srcs, crate_root = _transform_sources(ctx, ctx.files.srcs, getattr(ctx.file, "crate_root", None)) + srcs, crate_root = transform_sources(ctx, ctx.files.srcs, getattr(ctx.file, "crate_root", None)) # Optionally join compile data if crate.compile_data: @@ -411,8 +254,16 @@ def _rust_test_impl(ctx): else: compile_data_targets = depset(ctx.attr.compile_data) rustc_env_files = ctx.files.rustc_env_files + crate.rustc_env_files - rustc_env = dict(crate.rustc_env) - rustc_env.update(**ctx.attr.rustc_env) + + rustc_env = dict(crate._rustc_env_attr) + + # crate.rustc_env is already expanded upstream in rust_library rule implementation + data_paths = depset(direct = getattr(ctx.attr, "data", [])).to_list() + rustc_env.update(expand_dict_value_locations( + ctx, + ctx.attr.rustc_env, + data_paths, + )) # Build the test binary using the dependency's srcs. crate_info = rust_common.create_crate_info( @@ -426,6 +277,7 @@ def _rust_test_impl(ctx): output = output, edition = crate.edition, rustc_env = rustc_env, + _rustc_env_attr = ctx.attr.rustc_env, rustc_env_files = rustc_env_files, is_test = True, compile_data = compile_data, @@ -439,7 +291,7 @@ def _rust_test_impl(ctx): if not crate_root: crate_root_type = "lib" if ctx.attr.use_libtest_harness else "bin" crate_root = crate_root_src(ctx.attr.name, ctx.files.srcs, crate_root_type) - srcs, crate_root = _transform_sources(ctx, ctx.files.srcs, crate_root) + srcs, crate_root = transform_sources(ctx, ctx.files.srcs, crate_root) output_hash = determine_output_hash(crate_root, ctx.label) output = ctx.actions.declare_file( @@ -450,6 +302,13 @@ def _rust_test_impl(ctx): ), ) + data_paths = depset(direct = getattr(ctx.attr, "data", [])).to_list() + rustc_env = expand_dict_value_locations( + ctx, + ctx.attr.rustc_env, + data_paths, + ) + # Target is a standalone crate. Build the test binary as its own crate. crate_info = rust_common.create_crate_info( name = compute_crate_name(ctx.workspace_name, ctx.label, toolchain, ctx.attr.crate_name), @@ -461,7 +320,8 @@ def _rust_test_impl(ctx): aliases = ctx.attr.aliases, output = output, edition = get_edition(ctx.attr, toolchain, ctx.label), - rustc_env = ctx.attr.rustc_env, + rustc_env = rustc_env, + _rustc_env_attr = ctx.attr.rustc_env, rustc_env_files = ctx.files.rustc_env_files, is_test = True, compile_data = depset(ctx.files.compile_data), @@ -475,6 +335,7 @@ def _rust_test_impl(ctx): toolchain = toolchain, crate_info = crate_info, rust_flags = ["--test"] if ctx.attr.use_libtest_harness else ["--cfg", "test"], + skip_expanding_rustc_env = True, ) data = getattr(ctx.attr, "data", []) diff --git a/rust/private/rust_analyzer.bzl b/rust/private/rust_analyzer.bzl index 3eea6c0841..323944db81 100644 --- a/rust/private/rust_analyzer.bzl +++ b/rust/private/rust_analyzer.bzl @@ -116,7 +116,7 @@ def _rust_analyzer_aspect_impl(target, ctx): rust_analyzer_info = RustAnalyzerInfo( crate = crate_info, cfgs = cfgs, - env = getattr(ctx.rule.attr, "rustc_env", {}), + env = crate_info.rustc_env, deps = dep_infos, crate_specs = depset(direct = [crate_spec], transitive = [dep.crate_specs for dep in dep_infos]), proc_macro_dylib_path = find_proc_macro_dylib_path(toolchain, target), diff --git a/rust/private/rustc.bzl b/rust/private/rustc.bzl index 501e1403d2..6ae6f30ca1 100644 --- a/rust/private/rustc.bzl +++ b/rust/private/rustc.bzl @@ -787,7 +787,8 @@ def construct_arguments( remap_path_prefix = "", use_json_output = False, build_metadata = False, - force_depend_on_objects = False): + force_depend_on_objects = False, + skip_expanding_rustc_env = False): """Builds an Args object containing common rustc flags Args: @@ -817,6 +818,7 @@ def construct_arguments( use_json_output (bool): Have rustc emit json and process_wrapper parse json messages to output rendered output. build_metadata (bool): Generate CLI arguments for building *only* .rmeta files. This requires use_json_output. force_depend_on_objects (bool): Force using `.rlib` object files instead of metadata (`.rmeta`) files even if they are available. + skip_expanding_rustc_env (bool): Whether to skip expanding CrateInfo.rustc_env_attr Returns: tuple: A tuple of the following items @@ -1027,11 +1029,14 @@ def construct_arguments( env.update(toolchain.env) # Update environment with user provided variables. - env.update(expand_dict_value_locations( - ctx, - crate_info.rustc_env, - data_paths, - )) + if skip_expanding_rustc_env: + env.update(crate_info.rustc_env) + else: + env.update(expand_dict_value_locations( + ctx, + crate_info._rustc_env_attr, + data_paths, + )) # Ensure the sysroot is set for the target platform env["SYSROOT"] = toolchain.sysroot @@ -1079,21 +1084,29 @@ def rustc_compile_action( ctx, attr, toolchain, - crate_info, - output_hash = None, rust_flags = [], - force_all_deps_direct = False): + crate_type = None, + crate_info = None, + output_hash = None, + force_all_deps_direct = False, + # TODO: Remove create_crate_info_callback and skip_expanding_rustc_env attributes + # after all CrateInfo structs are constructed in rustc_compile_action + create_crate_info_callback = None, + skip_expanding_rustc_env = False): """Create and run a rustc compile action based on the current rule's attributes Args: ctx (ctx): The rule's context object attr (struct): Attributes to use for the rust compile action toolchain (rust_toolchain): The current `rust_toolchain` + crate_type: TODO crate_info (CrateInfo): The CrateInfo provider for the current target. output_hash (str, optional): The hashed path of the crate root. Defaults to None. rust_flags (list, optional): Additional flags to pass to rustc. Defaults to []. force_all_deps_direct (bool, optional): Whether to pass the transitive rlibs with --extern to the commandline as opposed to -L. + create_crate_info_callback: A callback to construct a mutable dict for constructor CrateInfo + skip_expanding_rustc_env (bool, optional): Whether to expand CrateInfo.rustc_env Returns: list: A list of the following providers: @@ -1101,6 +1114,18 @@ def rustc_compile_action( - (DepInfo): The transitive dependencies of this crate. - (DefaultInfo): The output file for this crate, and its runfiles. """ + # TODO: Remove create_crate_info_callback after all rustc_compile_action callers migrate to + # removing CrateInfo construction before `rust_compile_action + + crate_info_dict = None + if create_crate_info_callback != None: + if ctx == None or toolchain == None or crate_type == None or crate_info != None: + fail("FAIL", ctx, toolchain, crate_type) + crate_info_dict = create_crate_info_callback(ctx, toolchain, crate_type) + + if crate_info_dict != None: + crate_info = rust_common.create_crate_info(**crate_info_dict) + build_metadata = getattr(crate_info, "metadata", None) cc_toolchain, feature_configuration = find_cc_toolchain(ctx) @@ -1181,6 +1206,7 @@ def rustc_compile_action( force_all_deps_direct = force_all_deps_direct, stamp = stamp, use_json_output = bool(build_metadata), + skip_expanding_rustc_env = skip_expanding_rustc_env, ) args_metadata = None @@ -1210,6 +1236,8 @@ def rustc_compile_action( ) env = dict(ctx.configuration.default_shell_env) + + # this is the final list of env vars env.update(env_from_args) if hasattr(attr, "version") and attr.version != "0.0.0": @@ -1421,6 +1449,12 @@ def rustc_compile_action( ), ] + if crate_info_dict != None: + crate_info_dict.update({ + "rustc_env": env, + }) + crate_info = rust_common.create_crate_info(**crate_info_dict) + if crate_info.type in ["staticlib", "cdylib"]: # These rules are not supposed to be depended on by other rust targets, and # as such they shouldn't provide a CrateInfo. However, one may still want to diff --git a/rust/private/rustdoc.bzl b/rust/private/rustdoc.bzl index f7d9985e77..dac24b3e74 100644 --- a/rust/private/rustdoc.bzl +++ b/rust/private/rustdoc.bzl @@ -39,6 +39,7 @@ def _strip_crate_info_output(crate_info): output = None, metadata = None, edition = crate_info.edition, + _rustc_env_attr = crate_info._rustc_env_attr, rustc_env = crate_info.rustc_env, rustc_env_files = crate_info.rustc_env_files, is_test = crate_info.is_test, @@ -125,6 +126,7 @@ def rustdoc_compile_action( remap_path_prefix = None, rustdoc = True, force_depend_on_objects = is_test, + skip_expanding_rustc_env = True, ) # Because rustdoc tests compile tests outside of the sandbox, the sysroot diff --git a/rust/private/rustdoc_test.bzl b/rust/private/rustdoc_test.bzl index 79b0f4dd84..f580c27aec 100644 --- a/rust/private/rustdoc_test.bzl +++ b/rust/private/rustdoc_test.bzl @@ -122,6 +122,7 @@ def _rust_doc_test_impl(ctx): output = crate.output, edition = crate.edition, rustc_env = crate.rustc_env, + _rustc_env_attr = crate._rustc_env_attr, rustc_env_files = crate.rustc_env_files, is_test = True, compile_data = crate.compile_data, diff --git a/rust/private/utils.bzl b/rust/private/utils.bzl index 879e2b62c5..ddee229bed 100644 --- a/rust/private/utils.bzl +++ b/rust/private/utils.bzl @@ -14,6 +14,7 @@ """Utility functions not specific to the rust toolchain.""" +load("@bazel_skylib//lib:paths.bzl", "paths") load("@bazel_tools//tools/cpp:toolchain_utils.bzl", find_rules_cc_toolchain = "find_cpp_toolchain") load(":providers.bzl", "BuildInfo", "CrateGroupInfo", "CrateInfo", "DepInfo", "DepVariantInfo") @@ -725,3 +726,191 @@ def _shortest_src_with_basename(srcs, basename): if not shortest or len(f.dirname) < len(shortest.dirname): shortest = f return shortest + +def _determine_lib_name(name, crate_type, toolchain, lib_hash = None): + """See https://github.com/bazelbuild/rules_rust/issues/405 + + Args: + name (str): The name of the current target + crate_type (str): The `crate_type` + toolchain (rust_toolchain): The current `rust_toolchain` + lib_hash (str, optional): The hashed crate root path + + Returns: + str: A unique library name + """ + extension = None + prefix = "" + if crate_type in ("dylib", "cdylib", "proc-macro"): + extension = toolchain.dylib_ext + elif crate_type == "staticlib": + extension = toolchain.staticlib_ext + elif crate_type in ("lib", "rlib"): + # All platforms produce 'rlib' here + extension = ".rlib" + prefix = "lib" + elif crate_type == "bin": + fail("crate_type of 'bin' was detected in a rust_library. Please compile " + + "this crate as a rust_binary instead.") + + if not extension: + fail(("Unknown crate_type: {}. If this is a cargo-supported crate type, " + + "please file an issue!").format(crate_type)) + + prefix = "lib" + if toolchain.target_triple and toolchain.target_os == "windows" and crate_type not in ("lib", "rlib"): + prefix = "" + if toolchain.target_arch == "wasm32" and crate_type == "cdylib": + prefix = "" + + return "{prefix}{name}{lib_hash}{extension}".format( + prefix = prefix, + name = name, + lib_hash = "-" + lib_hash if lib_hash else "", + extension = extension, + ) + +def transform_sources(ctx, srcs, crate_root): + """Creates symlinks of the source files if needed. + + Rustc assumes that the source files are located next to the crate root. + In case of a mix between generated and non-generated source files, this + we violate this assumption, as part of the sources will be located under + bazel-out/... . In order to allow for targets that contain both generated + and non-generated source files, we generate symlinks for all non-generated + files. + + Args: + ctx (struct): The current rule's context. + srcs (List[File]): The sources listed in the `srcs` attribute + crate_root (File): The file specified in the `crate_root` attribute, + if it exists, otherwise None + + Returns: + Tuple(List[File], File): The transformed srcs and crate_root + """ + has_generated_sources = len([src for src in srcs if not src.is_source]) > 0 + + if not has_generated_sources: + return srcs, crate_root + + package_root = paths.dirname(paths.join(ctx.label.workspace_root, ctx.build_file_path)) + generated_sources = [_symlink_for_non_generated_source(ctx, src, package_root) for src in srcs if src != crate_root] + generated_root = crate_root + if crate_root: + generated_root = _symlink_for_non_generated_source(ctx, crate_root, package_root) + generated_sources.append(generated_root) + + return generated_sources, generated_root + +def get_edition(attr, toolchain, label): + """Returns the Rust edition from either the current rule's attributes or the current `rust_toolchain` + + Args: + attr (struct): The current rule's attributes + toolchain (rust_toolchain): The `rust_toolchain` for the current target + label (Label): The label of the target being built + + Returns: + str: The target Rust edition + """ + if getattr(attr, "edition"): + return attr.edition + elif not toolchain.default_edition: + fail("Attribute `edition` is required for {}.".format(label)) + else: + return toolchain.default_edition + +def _symlink_for_non_generated_source(ctx, src_file, package_root): + """Creates and returns a symlink for non-generated source files. + + This rule uses the full path to the source files and the rule directory to compute + the relative paths. This is needed, instead of using `short_path`, because of non-generated + source files in external repositories possibly returning relative paths depending on the + current version of Bazel. + + Args: + ctx (struct): The current rule's context. + src_file (File): The source file. + package_root (File): The full path to the directory containing the current rule. + + Returns: + File: The created symlink if a non-generated file, or the file itself. + """ + + if src_file.is_source or src_file.root.path != ctx.bin_dir.path: + src_short_path = paths.relativize(src_file.path, src_file.root.path) + src_symlink = ctx.actions.declare_file(paths.relativize(src_short_path, package_root)) + ctx.actions.symlink( + output = src_symlink, + target_file = src_file, + progress_message = "Creating symlink to source file: {}".format(src_file.path), + ) + return src_symlink + else: + return src_file + +def create_crate_info_dict(ctx, toolchain, crate_type): + """Creates a mutable dict() representing CrateInfo provider + + create_crate_info_dict is a *temporary* solution until create_crate_info is completely moved into + rustc_compile_action function. + + The function is currently used as a callback to support constructing CrateInfo in rustc_compile_action + to ensure `CrateInfo.rustc_env` is fully loaded with all the env vars passed to rustc. + + Args: + ctx (struct): The current rule's context + toolchain (toolchain): The rust toolchain + crate_type (String): one of lib|rlib|dylib|staticlib|cdylib|proc-macro + + Returns: + File: The created symlink if a non-generated file, or the file itself. + """ + crate_name = compute_crate_name(ctx.workspace_name, ctx.label, toolchain, ctx.attr.crate_name) + crate_root = getattr(ctx.file, "crate_root", None) + if not crate_root: + crate_root = crate_root_src(ctx.attr.name, ctx.files.srcs, crate_type) + srcs, crate_root = transform_sources(ctx, ctx.files.srcs, crate_root) + + if crate_type in ["cdylib", "staticlib"]: + output_hash = None + else: + output_hash = determine_output_hash(crate_root, ctx.label) + + deps = transform_deps(ctx.attr.deps) + proc_macro_deps = transform_deps(ctx.attr.proc_macro_deps + get_import_macro_deps(ctx)) + rust_lib_name = _determine_lib_name( + crate_name, + crate_type, + toolchain, + output_hash, + ) + rust_lib = ctx.actions.declare_file(rust_lib_name) + rust_metadata = None + if can_build_metadata(toolchain, ctx, crate_type) and not ctx.attr.disable_pipelining: + rust_metadata = ctx.actions.declare_file( + paths.replace_extension(rust_lib_name, ".rmeta"), + sibling = rust_lib, + ) + + return dict( + name = crate_name, + type = crate_type, + root = crate_root, + srcs = depset(srcs), + deps = depset(deps), + proc_macro_deps = depset(proc_macro_deps), + aliases = ctx.attr.aliases, + output = rust_lib, + metadata = rust_metadata, + edition = get_edition(ctx.attr, toolchain, ctx.label), + rustc_env = ctx.attr.rustc_env, + rustc_env_files = ctx.files.rustc_env_files, + is_test = False, + data = depset(ctx.files.data), + compile_data = depset(ctx.files.compile_data), + compile_data_targets = depset(ctx.attr.compile_data), + owner = ctx.label, + _rustc_env_attr = ctx.attr.rustc_env, + ) diff --git a/test/unit/consistent_crate_name/with_modified_crate_name.bzl b/test/unit/consistent_crate_name/with_modified_crate_name.bzl index f06ddab59a..335ec12865 100644 --- a/test/unit/consistent_crate_name/with_modified_crate_name.bzl +++ b/test/unit/consistent_crate_name/with_modified_crate_name.bzl @@ -49,6 +49,7 @@ def _with_modified_crate_name_impl(ctx): compile_data = depset([]), compile_data_targets = depset([]), rustc_env = {}, + _rustc_env_attr = {}, is_test = False, ), output_hash = output_hash, diff --git a/test/unit/force_all_deps_direct/generator.bzl b/test/unit/force_all_deps_direct/generator.bzl index 571b47e841..f56d09c2c8 100644 --- a/test/unit/force_all_deps_direct/generator.bzl +++ b/test/unit/force_all_deps_direct/generator.bzl @@ -66,6 +66,7 @@ EOF compile_data = depset([]), compile_data_targets = depset([]), rustc_env = {}, + _rustc_env_attr = {}, is_test = False, ), output_hash = output_hash, diff --git a/test/unit/pipelined_compilation/wrap.bzl b/test/unit/pipelined_compilation/wrap.bzl index 8c09b4bc65..b00f761a9a 100644 --- a/test/unit/pipelined_compilation/wrap.bzl +++ b/test/unit/pipelined_compilation/wrap.bzl @@ -76,6 +76,7 @@ EOF compile_data = depset([]), compile_data_targets = depset([]), rustc_env = {}, + _rustc_env_attr = {}, is_test = False, ), output_hash = output_hash,