blob: 490bfa1d6504a0253e2e00a47e18add4007587df [file] [log] [blame]
Brian Silvermancc09f182022-03-09 15:40:20 -08001# Copyright 2018 The Bazel Authors. All rights reserved.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15"""Functionality for constructing actions that invoke the Rust compiler"""
16
17load(
18 "@bazel_tools//tools/build_defs/cc:action_names.bzl",
19 "CPP_LINK_EXECUTABLE_ACTION_NAME",
20)
21load("//rust/private:common.bzl", "rust_common")
22load("//rust/private:providers.bzl", _BuildInfo = "BuildInfo")
23load("//rust/private:stamp.bzl", "is_stamping_enabled")
24load(
25 "//rust/private:utils.bzl",
26 "abs",
27 "expand_dict_value_locations",
28 "expand_list_element_locations",
29 "find_cc_toolchain",
Brian Silverman5f6f2762022-08-13 19:30:05 -070030 "get_lib_name_default",
31 "get_lib_name_for_windows",
Brian Silvermancc09f182022-03-09 15:40:20 -080032 "get_preferred_artifact",
33 "is_exec_configuration",
34 "make_static_lib_symlink",
35 "relativize",
36)
37
38BuildInfo = _BuildInfo
39
40AliasableDepInfo = provider(
41 doc = "A provider mapping an alias name to a Crate's information.",
42 fields = {
43 "dep": "CrateInfo",
44 "name": "str",
45 },
46)
47
48_error_format_values = ["human", "json", "short"]
49
50ErrorFormatInfo = provider(
51 doc = "Set the --error-format flag for all rustc invocations",
52 fields = {"error_format": "(string) [" + ", ".join(_error_format_values) + "]"},
53)
54
55ExtraRustcFlagsInfo = provider(
56 doc = "Pass each value as an additional flag to non-exec rustc invocations",
57 fields = {"extra_rustc_flags": "List[string] Extra flags to pass to rustc in non-exec configuration"},
58)
59
60ExtraExecRustcFlagsInfo = provider(
61 doc = "Pass each value as an additional flag to exec rustc invocations",
62 fields = {"extra_exec_rustc_flags": "List[string] Extra flags to pass to rustc in exec configuration"},
63)
64
Brian Silverman5f6f2762022-08-13 19:30:05 -070065IsProcMacroDepInfo = provider(
66 doc = "Records if this is a transitive dependency of a proc-macro.",
67 fields = {"is_proc_macro_dep": "Boolean"},
68)
69
70def _is_proc_macro_dep_impl(ctx):
71 return IsProcMacroDepInfo(is_proc_macro_dep = ctx.build_setting_value)
72
73is_proc_macro_dep = rule(
74 doc = "Records if this is a transitive dependency of a proc-macro.",
75 implementation = _is_proc_macro_dep_impl,
76 build_setting = config.bool(flag = True),
77)
78
79IsProcMacroDepEnabledInfo = provider(
80 doc = "Enables the feature to record if a library is a transitive dependency of a proc-macro.",
81 fields = {"enabled": "Boolean"},
82)
83
84def _is_proc_macro_dep_enabled_impl(ctx):
85 return IsProcMacroDepEnabledInfo(enabled = ctx.build_setting_value)
86
87is_proc_macro_dep_enabled = rule(
88 doc = "Enables the feature to record if a library is a transitive dependency of a proc-macro.",
89 implementation = _is_proc_macro_dep_enabled_impl,
90 build_setting = config.bool(flag = True),
91)
92
Brian Silvermancc09f182022-03-09 15:40:20 -080093def _get_rustc_env(attr, toolchain, crate_name):
94 """Gathers rustc environment variables
95
96 Args:
97 attr (struct): The current target's attributes
98 toolchain (rust_toolchain): The current target's rust toolchain context
99 crate_name (str): The name of the crate to be compiled
100
101 Returns:
102 dict: Rustc environment variables
103 """
104 version = attr.version if hasattr(attr, "version") else "0.0.0"
105 major, minor, patch = version.split(".", 2)
106 if "-" in patch:
107 patch, pre = patch.split("-", 1)
108 else:
109 pre = ""
Brian Silverman5f6f2762022-08-13 19:30:05 -0700110 result = {
Brian Silvermancc09f182022-03-09 15:40:20 -0800111 "CARGO_CFG_TARGET_ARCH": toolchain.target_arch,
112 "CARGO_CFG_TARGET_OS": toolchain.os,
113 "CARGO_CRATE_NAME": crate_name,
114 "CARGO_PKG_AUTHORS": "",
115 "CARGO_PKG_DESCRIPTION": "",
116 "CARGO_PKG_HOMEPAGE": "",
117 "CARGO_PKG_NAME": attr.name,
118 "CARGO_PKG_VERSION": version,
119 "CARGO_PKG_VERSION_MAJOR": major,
120 "CARGO_PKG_VERSION_MINOR": minor,
121 "CARGO_PKG_VERSION_PATCH": patch,
122 "CARGO_PKG_VERSION_PRE": pre,
123 }
Brian Silverman5f6f2762022-08-13 19:30:05 -0700124 if hasattr(attr, "_is_proc_macro_dep_enabled") and attr._is_proc_macro_dep_enabled[IsProcMacroDepEnabledInfo].enabled:
125 is_proc_macro_dep = "0"
126 if hasattr(attr, "_is_proc_macro_dep") and attr._is_proc_macro_dep[IsProcMacroDepInfo].is_proc_macro_dep:
127 is_proc_macro_dep = "1"
128 result["BAZEL_RULES_RUST_IS_PROC_MACRO_DEP"] = is_proc_macro_dep
129 return result
Brian Silvermancc09f182022-03-09 15:40:20 -0800130
131def get_compilation_mode_opts(ctx, toolchain):
132 """Gathers rustc flags for the current compilation mode (opt/debug)
133
134 Args:
135 ctx (ctx): The current rule's context object
136 toolchain (rust_toolchain): The current rule's `rust_toolchain`
137
138 Returns:
139 struct: See `_rust_toolchain_impl` for more details
140 """
141 comp_mode = ctx.var["COMPILATION_MODE"]
142 if not comp_mode in toolchain.compilation_mode_opts:
143 fail("Unrecognized compilation mode {} for toolchain.".format(comp_mode))
144
145 return toolchain.compilation_mode_opts[comp_mode]
146
147def _are_linkstamps_supported(feature_configuration, has_grep_includes):
148 # Are linkstamps supported by the C++ toolchain?
149 return (cc_common.is_enabled(feature_configuration = feature_configuration, feature_name = "linkstamps") and
150 # Is Bazel recent enough to support Starlark linkstamps?
151 hasattr(cc_common, "register_linkstamp_compile_action") and
152 # The current rule doesn't define _grep_includes attribute; this
153 # attribute is required for compiling linkstamps.
154 has_grep_includes)
155
156def _should_use_pic(cc_toolchain, feature_configuration, crate_type):
Brian Silverman5f6f2762022-08-13 19:30:05 -0700157 """Whether or not [PIC][pic] should be enabled
158
159 [pic]: https://en.wikipedia.org/wiki/Position-independent_code
160
161 Args:
162 cc_toolchain (CcToolchainInfo): The current `cc_toolchain`.
163 feature_configuration (FeatureConfiguration): Feature configuration to be queried.
164 crate_type (str): A Rust target's crate type.
165
166 Returns:
167 bool: Whether or not [PIC][pic] should be enabled.
168 """
169 if crate_type in ("cdylib", "dylib"):
Brian Silvermancc09f182022-03-09 15:40:20 -0800170 return cc_toolchain.needs_pic_for_dynamic_libraries(feature_configuration = feature_configuration)
171 return False
172
Brian Silverman5f6f2762022-08-13 19:30:05 -0700173def _is_proc_macro(crate_info):
174 return "proc-macro" in (crate_info.type, crate_info.wrapped_crate_type)
175
Brian Silvermancc09f182022-03-09 15:40:20 -0800176def collect_deps(
177 deps,
178 proc_macro_deps,
179 aliases,
180 are_linkstamps_supported = False):
181 """Walks through dependencies and collects the transitive dependencies.
182
183 Args:
184 deps (list): The deps from ctx.attr.deps.
185 proc_macro_deps (list): The proc_macro deps from ctx.attr.proc_macro_deps.
186 aliases (dict): A dict mapping aliased targets to their actual Crate information.
187 are_linkstamps_supported (bool): Whether the current rule and the toolchain support building linkstamps..
188
189 Returns:
190 tuple: Returns a tuple of:
191 DepInfo,
192 BuildInfo,
193 linkstamps (depset[CcLinkstamp]): A depset of CcLinkstamps that need to be compiled and linked into all linked binaries.
194
195 """
196 direct_crates = []
197 transitive_crates = []
198 transitive_noncrates = []
199 transitive_build_infos = []
200 transitive_link_search_paths = []
201 build_info = None
202 linkstamps = []
203 transitive_crate_outputs = []
Brian Silverman5f6f2762022-08-13 19:30:05 -0700204 transitive_metadata_outputs = []
Brian Silvermancc09f182022-03-09 15:40:20 -0800205
206 aliases = {k.label: v for k, v in aliases.items()}
207 for dep in depset(transitive = [deps, proc_macro_deps]).to_list():
208 (crate_info, dep_info) = _get_crate_and_dep_info(dep)
209 cc_info = _get_cc_info(dep)
210 dep_build_info = _get_build_info(dep)
211
212 if cc_info and are_linkstamps_supported:
213 linkstamps.append(cc_info.linking_context.linkstamps())
214
215 if crate_info:
216 # This dependency is a rust_library
217
218 # When crate_info.owner is set, we use it. When the dep type is Target we get the
219 # label from dep.label
220 owner = getattr(crate_info, "owner", dep.label if type(dep) == "Target" else None)
221
222 direct_crates.append(AliasableDepInfo(
223 name = aliases.get(owner, crate_info.name),
224 dep = crate_info,
225 ))
226
Brian Silverman5f6f2762022-08-13 19:30:05 -0700227 transitive_crates.append(
228 depset(
229 [crate_info],
230 transitive = [] if _is_proc_macro(crate_info) else [dep_info.transitive_crates],
231 ),
232 )
233
234 # If this dependency produces metadata, add it to the metadata outputs.
235 # If it doesn't (for example a custom library that exports crate_info),
236 # we depend on crate_info.output.
237 depend_on = crate_info.metadata
238 if not crate_info.metadata:
239 depend_on = crate_info.output
240
241 # If this dependency is a proc_macro, it still can be used for lib crates
242 # that produce metadata.
243 # In that case, we don't depend on its metadata dependencies.
244 transitive_metadata_outputs.append(
245 depset(
246 [depend_on],
247 transitive = [] if _is_proc_macro(crate_info) else [dep_info.transitive_metadata_outputs],
248 ),
249 )
250
Brian Silvermancc09f182022-03-09 15:40:20 -0800251 transitive_crate_outputs.append(
252 depset(
253 [crate_info.output],
Brian Silverman5f6f2762022-08-13 19:30:05 -0700254 transitive = [] if _is_proc_macro(crate_info) else [dep_info.transitive_crate_outputs],
Brian Silvermancc09f182022-03-09 15:40:20 -0800255 ),
256 )
Brian Silverman5f6f2762022-08-13 19:30:05 -0700257
258 if "proc-macro" not in [crate_info.type, crate_info.wrapped_crate_type]:
259 transitive_noncrates.append(dep_info.transitive_noncrates)
260 transitive_link_search_paths.append(dep_info.link_search_path_files)
261
Brian Silvermancc09f182022-03-09 15:40:20 -0800262 transitive_build_infos.append(dep_info.transitive_build_infos)
Brian Silvermancc09f182022-03-09 15:40:20 -0800263
264 elif cc_info:
265 # This dependency is a cc_library
266 transitive_noncrates.append(cc_info.linking_context.linker_inputs)
267 elif dep_build_info:
268 if build_info:
269 fail("Several deps are providing build information, " +
270 "only one is allowed in the dependencies")
271 build_info = dep_build_info
272 transitive_build_infos.append(depset([build_info]))
273 transitive_link_search_paths.append(depset([build_info.link_search_paths]))
274 else:
275 fail("rust targets can only depend on rust_library, rust_*_library or cc_library " +
276 "targets.")
277
278 transitive_crates_depset = depset(transitive = transitive_crates)
279
280 return (
281 rust_common.dep_info(
282 direct_crates = depset(direct_crates),
283 transitive_crates = transitive_crates_depset,
284 transitive_noncrates = depset(
285 transitive = transitive_noncrates,
286 order = "topological", # dylib link flag ordering matters.
287 ),
288 transitive_crate_outputs = depset(transitive = transitive_crate_outputs),
Brian Silverman5f6f2762022-08-13 19:30:05 -0700289 transitive_metadata_outputs = depset(transitive = transitive_metadata_outputs),
Brian Silvermancc09f182022-03-09 15:40:20 -0800290 transitive_build_infos = depset(transitive = transitive_build_infos),
291 link_search_path_files = depset(transitive = transitive_link_search_paths),
292 dep_env = build_info.dep_env if build_info else None,
293 ),
294 build_info,
295 depset(transitive = linkstamps),
296 )
297
298def _collect_libs_from_linker_inputs(linker_inputs, use_pic):
299 # TODO: We could let the user choose how to link, instead of always preferring to link static libraries.
300 return [
301 get_preferred_artifact(lib, use_pic)
302 for li in linker_inputs
303 for lib in li.libraries
304 ]
305
306def _get_crate_and_dep_info(dep):
307 if type(dep) == "Target" and rust_common.crate_info in dep:
308 return (dep[rust_common.crate_info], dep[rust_common.dep_info])
309 elif type(dep) == "struct" and hasattr(dep, "crate_info"):
310 return (dep.crate_info, dep.dep_info)
311 return (None, None)
312
313def _get_cc_info(dep):
314 if type(dep) == "Target" and CcInfo in dep:
315 return dep[CcInfo]
316 elif type(dep) == "struct" and hasattr(dep, "cc_info"):
317 return dep.cc_info
318 return None
319
320def _get_build_info(dep):
321 if type(dep) == "Target" and BuildInfo in dep:
322 return dep[BuildInfo]
323 elif type(dep) == "struct" and hasattr(dep, "build_info"):
324 return dep.build_info
325 return None
326
327def get_cc_user_link_flags(ctx):
328 """Get the current target's linkopt flags
329
330 Args:
331 ctx (ctx): The current rule's context object
332
333 Returns:
334 depset: The flags passed to Bazel by --linkopt option.
335 """
336 return ctx.fragments.cpp.linkopts
337
338def get_linker_and_args(ctx, attr, cc_toolchain, feature_configuration, rpaths):
339 """Gathers cc_common linker information
340
341 Args:
342 ctx (ctx): The current target's context object
343 attr (struct): Attributes to use in gathering linker args
344 cc_toolchain (CcToolchain): cc_toolchain for which we are creating build variables.
345 feature_configuration (FeatureConfiguration): Feature configuration to be queried.
346 rpaths (depset): Depset of directories where loader will look for libraries at runtime.
347
348
349 Returns:
350 tuple: A tuple of the following items:
351 - (str): The tool path for given action.
352 - (sequence): A flattened command line flags for given action.
353 - (dict): Environment variables to be set for given action.
354 """
355 user_link_flags = get_cc_user_link_flags(ctx)
356
357 # Add linkopt's from dependencies. This includes linkopts from transitive
358 # dependencies since they get merged up.
359 for dep in getattr(attr, "deps", []):
360 if CcInfo in dep and dep[CcInfo].linking_context:
361 for linker_input in dep[CcInfo].linking_context.linker_inputs.to_list():
362 for flag in linker_input.user_link_flags:
363 user_link_flags.append(flag)
364 link_variables = cc_common.create_link_variables(
365 feature_configuration = feature_configuration,
366 cc_toolchain = cc_toolchain,
367 is_linking_dynamic_library = False,
368 runtime_library_search_directories = rpaths,
369 user_link_flags = user_link_flags,
370 )
371 link_args = cc_common.get_memory_inefficient_command_line(
372 feature_configuration = feature_configuration,
373 action_name = CPP_LINK_EXECUTABLE_ACTION_NAME,
374 variables = link_variables,
375 )
376 link_env = cc_common.get_environment_variables(
377 feature_configuration = feature_configuration,
378 action_name = CPP_LINK_EXECUTABLE_ACTION_NAME,
379 variables = link_variables,
380 )
381 ld = cc_common.get_tool_for_action(
382 feature_configuration = feature_configuration,
383 action_name = CPP_LINK_EXECUTABLE_ACTION_NAME,
384 )
385
386 return ld, link_args, link_env
387
388def _process_build_scripts(
389 build_info,
390 dep_info,
391 compile_inputs):
392 """Gathers the outputs from a target's `cargo_build_script` action.
393
394 Args:
395 build_info (BuildInfo): The target Build's dependency info.
396 dep_info (DepInfo): The Depinfo provider form the target Crate's set of inputs.
397 compile_inputs (depset): A set of all files that will participate in the build.
398
399 Returns:
400 tuple: A tuple: A tuple of the following items:
401 - (depset[File]): A list of all build info `OUT_DIR` File objects
402 - (str): The `OUT_DIR` of the current build info
403 - (File): An optional path to a generated environment file from a `cargo_build_script` target
404 - (depset[File]): All direct and transitive build flags from the current build info.
405 """
406 extra_inputs, out_dir, build_env_file, build_flags_files = _create_extra_input_args(build_info, dep_info)
407 compile_inputs = depset(transitive = [extra_inputs, compile_inputs])
408 return compile_inputs, out_dir, build_env_file, build_flags_files
409
410def _symlink_for_ambiguous_lib(actions, toolchain, crate_info, lib):
411 """Constructs a disambiguating symlink for a library dependency.
412
413 Args:
414 actions (Actions): The rule's context actions object.
415 toolchain: The Rust toolchain object.
416 crate_info (CrateInfo): The target crate's info.
417 lib (File): The library to symlink to.
418
419 Returns:
420 (File): The disambiguating symlink for the library.
421 """
422 # FIXME: Once the relative order part of the native-link-modifiers rustc
423 # feature is stable, we should be able to eliminate the need to construct
424 # symlinks by passing the full paths to the libraries.
425 # https://github.com/rust-lang/rust/issues/81490.
426
427 # Take the absolute value of hash() since it could be negative.
428 path_hash = abs(hash(lib.path))
Brian Silverman5f6f2762022-08-13 19:30:05 -0700429 lib_name = get_lib_name_for_windows(lib) if toolchain.os.startswith("windows") else get_lib_name_default(lib)
Brian Silvermancc09f182022-03-09 15:40:20 -0800430
431 prefix = "lib"
432 extension = ".a"
433 if toolchain.os.startswith("windows"):
434 prefix = ""
435 extension = ".lib"
436
437 # Ensure the symlink follows the lib<name>.a pattern on Unix-like platforms
438 # or <name>.lib on Windows.
439 # Add a hash of the original library path to disambiguate libraries with the same basename.
440 symlink_name = "{}{}-{}{}".format(prefix, lib_name, path_hash, extension)
441
442 # Add the symlink to a target crate-specific _ambiguous_libs/ subfolder,
443 # to avoid possible collisions with sibling crates that may depend on the
444 # same ambiguous libraries.
445 symlink = actions.declare_file("_ambiguous_libs/" + crate_info.output.basename + "/" + symlink_name)
446 actions.symlink(
447 output = symlink,
448 target_file = lib,
449 progress_message = "Creating symlink to ambiguous lib: {}".format(lib.path),
450 )
451 return symlink
452
453def _disambiguate_libs(actions, toolchain, crate_info, dep_info, use_pic):
454 """Constructs disambiguating symlinks for ambiguous library dependencies.
455
456 The symlinks are all created in a _ambiguous_libs/ subfolder specific to
457 the target crate to avoid possible collisions with sibling crates that may
458 depend on the same ambiguous libraries.
459
460 Args:
461 actions (Actions): The rule's context actions object.
462 toolchain: The Rust toolchain object.
463 crate_info (CrateInfo): The target crate's info.
464 dep_info: (DepInfo): The target crate's dependency info.
465 use_pic: (boolean): Whether the build should use PIC.
466
467 Returns:
468 dict[String, File]: A mapping from ambiguous library paths to their
469 disambiguating symlink.
470 """
471 # FIXME: Once the relative order part of the native-link-modifiers rustc
472 # feature is stable, we should be able to eliminate the need to construct
473 # symlinks by passing the full paths to the libraries.
474 # https://github.com/rust-lang/rust/issues/81490.
475
476 # A dictionary from file paths of ambiguous libraries to the corresponding
477 # symlink.
478 ambiguous_libs = {}
479
480 # A dictionary maintaining a mapping from preferred library name to the
481 # last visited artifact with that name.
482 visited_libs = {}
483 for link_input in dep_info.transitive_noncrates.to_list():
484 for lib in link_input.libraries:
485 # FIXME: Dynamic libs are not disambiguated right now, there are
486 # cases where those have a non-standard name with version (e.g.,
487 # //test/unit/versioned_libs). We hope that the link modifiers
488 # stabilization will come before we need to make this work.
489 if _is_dylib(lib):
490 continue
491 artifact = get_preferred_artifact(lib, use_pic)
Brian Silverman5f6f2762022-08-13 19:30:05 -0700492 name = get_lib_name_for_windows(artifact) if toolchain.os.startswith("windows") else get_lib_name_default(artifact)
Brian Silvermancc09f182022-03-09 15:40:20 -0800493
494 # On Linux-like platforms, normally library base names start with
495 # `lib`, following the pattern `lib[name].(a|lo)` and we pass
496 # -lstatic=name.
497 # On Windows, the base name looks like `name.lib` and we pass
498 # -lstatic=name.
499 # FIXME: Under the native-link-modifiers unstable rustc feature,
500 # we could use -lstatic:+verbatim instead.
501 needs_symlink_to_standardize_name = (
502 (toolchain.os.startswith("linux") or toolchain.os.startswith("mac") or toolchain.os.startswith("darwin")) and
503 artifact.basename.endswith(".a") and not artifact.basename.startswith("lib")
504 ) or (
505 toolchain.os.startswith("windows") and not artifact.basename.endswith(".lib")
506 )
507
508 # Detect cases where we need to disambiguate library dependencies
509 # by constructing symlinks.
510 if (
511 needs_symlink_to_standardize_name or
512 # We have multiple libraries with the same name.
513 (name in visited_libs and visited_libs[name].path != artifact.path)
514 ):
515 # Disambiguate the previously visited library (if we just detected
516 # that it is ambiguous) and the current library.
517 if name in visited_libs:
518 old_path = visited_libs[name].path
519 if old_path not in ambiguous_libs:
520 ambiguous_libs[old_path] = _symlink_for_ambiguous_lib(actions, toolchain, crate_info, visited_libs[name])
521 ambiguous_libs[artifact.path] = _symlink_for_ambiguous_lib(actions, toolchain, crate_info, artifact)
522
523 visited_libs[name] = artifact
524 return ambiguous_libs
525
Brian Silverman5f6f2762022-08-13 19:30:05 -0700526def _depend_on_metadata(crate_info, force_depend_on_objects):
527 """Determines if we can depend on metadata for this crate.
528
529 By default (when pipelining is disabled or when the crate type needs to link against
530 objects) we depend on the set of object files (.rlib).
531 When pipelining is enabled and the crate type supports depending on metadata,
532 we depend on metadata files only (.rmeta).
533 In some rare cases, even if both of those conditions are true, we still want to
534 depend on objects. This is what force_depend_on_objects is.
535
536 Args:
537 crate_info (CrateInfo): The Crate to determine this for.
538 force_depend_on_objects (bool): if set we will not depend on metadata.
539
540 Returns:
541 Whether we can depend on metadata for this crate.
542 """
543 if force_depend_on_objects:
544 return False
545
546 return crate_info.type in ("rlib", "lib")
547
Brian Silvermancc09f182022-03-09 15:40:20 -0800548def collect_inputs(
549 ctx,
550 file,
551 files,
552 linkstamps,
553 toolchain,
554 cc_toolchain,
555 feature_configuration,
556 crate_info,
557 dep_info,
558 build_info,
Brian Silverman5f6f2762022-08-13 19:30:05 -0700559 stamp = False,
560 force_depend_on_objects = False,
561 experimental_use_cc_common_link = False):
Brian Silvermancc09f182022-03-09 15:40:20 -0800562 """Gather's the inputs and required input information for a rustc action
563
564 Args:
565 ctx (ctx): The rule's context object.
566 file (struct): A struct containing files defined in label type attributes marked as `allow_single_file`.
567 files (list): A list of all inputs (`ctx.files`).
568 linkstamps (depset): A depset of CcLinkstamps that need to be compiled and linked into all linked binaries.
569 toolchain (rust_toolchain): The current `rust_toolchain`.
570 cc_toolchain (CcToolchainInfo): The current `cc_toolchain`.
571 feature_configuration (FeatureConfiguration): Feature configuration to be queried.
572 crate_info (CrateInfo): The Crate information of the crate to process build scripts for.
573 dep_info (DepInfo): The target Crate's dependency information.
574 build_info (BuildInfo): The target Crate's build settings.
575 stamp (bool, optional): Whether or not workspace status stamping is enabled. For more details see
576 https://docs.bazel.build/versions/main/user-manual.html#flag--stamp
Brian Silverman5f6f2762022-08-13 19:30:05 -0700577 force_depend_on_objects (bool, optional): Forces dependencies of this rule to be objects rather than
578 metadata, even for libraries. This is used in rustdoc tests.
579 experimental_use_cc_common_link (bool, optional): Whether rules_rust uses cc_common.link to link
580 rust binaries.
Brian Silvermancc09f182022-03-09 15:40:20 -0800581
582 Returns:
583 tuple: A tuple: A tuple of the following items:
584 - (list): A list of all build info `OUT_DIR` File objects
585 - (str): The `OUT_DIR` of the current build info
586 - (File): An optional path to a generated environment file from a `cargo_build_script` target
587 - (depset[File]): All direct and transitive build flag files from the current build info
588 - (list[File]): Linkstamp outputs
589 - (dict[String, File]): Ambiguous libs, see `_disambiguate_libs`.
590 """
591 linker_script = getattr(file, "linker_script") if hasattr(file, "linker_script") else None
592
593 linker_depset = cc_toolchain.all_files
594
595 use_pic = _should_use_pic(cc_toolchain, feature_configuration, crate_info.type)
596
597 # Pass linker inputs only for linking-like actions, not for example where
598 # the output is rlib. This avoids quadratic behavior where transitive noncrates are
599 # flattened on each transitive rust_library dependency.
600 additional_transitive_inputs = []
601 ambiguous_libs = {}
602 if crate_info.type in ("staticlib", "proc-macro"):
603 additional_transitive_inputs = _collect_libs_from_linker_inputs(
604 dep_info.transitive_noncrates.to_list(),
605 use_pic,
606 )
607 elif crate_info.type in ("bin", "dylib", "cdylib"):
608 linker_inputs = dep_info.transitive_noncrates.to_list()
609 ambiguous_libs = _disambiguate_libs(ctx.actions, toolchain, crate_info, dep_info, use_pic)
610 additional_transitive_inputs = _collect_libs_from_linker_inputs(linker_inputs, use_pic) + [
611 additional_input
612 for linker_input in linker_inputs
613 for additional_input in linker_input.additional_inputs
614 ] + ambiguous_libs.values()
615
616 # Compute linkstamps. Use the inputs of the binary as inputs to the
617 # linkstamp action to ensure linkstamps are rebuilt whenever binary inputs
618 # change.
619 linkstamp_outs = []
620
Brian Silverman5f6f2762022-08-13 19:30:05 -0700621 transitive_crate_outputs = dep_info.transitive_crate_outputs
622 if _depend_on_metadata(crate_info, force_depend_on_objects):
623 transitive_crate_outputs = dep_info.transitive_metadata_outputs
624
Brian Silvermancc09f182022-03-09 15:40:20 -0800625 nolinkstamp_compile_inputs = depset(
626 getattr(files, "data", []) +
627 ([build_info.rustc_env, build_info.flags] if build_info else []) +
628 ([toolchain.target_json] if toolchain.target_json else []) +
629 ([] if linker_script == None else [linker_script]),
630 transitive = [
631 linker_depset,
632 crate_info.srcs,
Brian Silverman5f6f2762022-08-13 19:30:05 -0700633 transitive_crate_outputs,
Brian Silvermancc09f182022-03-09 15:40:20 -0800634 depset(additional_transitive_inputs),
635 crate_info.compile_data,
636 toolchain.all_files,
637 ],
638 )
639
Brian Silverman5f6f2762022-08-13 19:30:05 -0700640 # Register linkstamps when linking with rustc (when linking with
641 # cc_common.link linkstamps are handled by cc_common.link itself).
642 if not experimental_use_cc_common_link and crate_info.type in ("bin", "cdylib"):
Brian Silvermancc09f182022-03-09 15:40:20 -0800643 # There is no other way to register an action for each member of a depset than
644 # flattening the depset as of 2021-10-12. Luckily, usually there is only one linkstamp
645 # in a build, and we only flatten the list on binary targets that perform transitive linking,
646 # so it's extremely unlikely that this call to `to_list()` will ever be a performance
647 # problem.
648 for linkstamp in linkstamps.to_list():
649 # The linkstamp output path is based on the binary crate
650 # name and the input linkstamp path. This is to disambiguate
651 # the linkstamp outputs produced by multiple binary crates
652 # that depend on the same linkstamp. We use the same pattern
653 # for the output name as the one used by native cc rules.
654 out_name = "_objs/" + crate_info.output.basename + "/" + linkstamp.file().path[:-len(linkstamp.file().extension)] + "o"
655 linkstamp_out = ctx.actions.declare_file(out_name)
656 linkstamp_outs.append(linkstamp_out)
657 cc_common.register_linkstamp_compile_action(
658 actions = ctx.actions,
659 cc_toolchain = cc_toolchain,
660 feature_configuration = feature_configuration,
661 grep_includes = ctx.file._grep_includes,
662 source_file = linkstamp.file(),
663 output_file = linkstamp_out,
664 compilation_inputs = linkstamp.hdrs(),
665 inputs_for_validation = nolinkstamp_compile_inputs,
666 label_replacement = str(ctx.label),
667 output_replacement = crate_info.output.path,
668 )
669
Brian Silverman5f6f2762022-08-13 19:30:05 -0700670 # If stamping is enabled include the volatile and stable status info file
671 stamp_info = [ctx.version_file, ctx.info_file] if stamp else []
Brian Silvermancc09f182022-03-09 15:40:20 -0800672
673 compile_inputs = depset(
674 linkstamp_outs + stamp_info,
675 transitive = [
676 nolinkstamp_compile_inputs,
677 ],
678 )
679
Brian Silverman5f6f2762022-08-13 19:30:05 -0700680 # For backwards compatibility, we also check the value of the `rustc_env_files` attribute when
681 # `crate_info.rustc_env_files` is not populated.
682 build_env_files = crate_info.rustc_env_files if crate_info.rustc_env_files else getattr(files, "rustc_env_files", [])
Brian Silvermancc09f182022-03-09 15:40:20 -0800683 compile_inputs, out_dir, build_env_file, build_flags_files = _process_build_scripts(build_info, dep_info, compile_inputs)
684 if build_env_file:
685 build_env_files = [f for f in build_env_files] + [build_env_file]
686 compile_inputs = depset(build_env_files, transitive = [compile_inputs])
687
688 return compile_inputs, out_dir, build_env_files, build_flags_files, linkstamp_outs, ambiguous_libs
689
690def construct_arguments(
691 ctx,
692 attr,
693 file,
694 toolchain,
695 tool_path,
696 cc_toolchain,
697 feature_configuration,
698 crate_info,
699 dep_info,
700 linkstamp_outs,
701 ambiguous_libs,
702 output_hash,
703 rust_flags,
704 out_dir,
705 build_env_files,
706 build_flags_files,
707 emit = ["dep-info", "link"],
708 force_all_deps_direct = False,
709 force_link = False,
710 stamp = False,
Brian Silverman5f6f2762022-08-13 19:30:05 -0700711 remap_path_prefix = ".",
712 use_json_output = False,
713 build_metadata = False,
714 force_depend_on_objects = False):
Brian Silvermancc09f182022-03-09 15:40:20 -0800715 """Builds an Args object containing common rustc flags
716
717 Args:
718 ctx (ctx): The rule's context object
719 attr (struct): The attributes for the target. These may be different from ctx.attr in an aspect context.
720 file (struct): A struct containing files defined in label type attributes marked as `allow_single_file`.
721 toolchain (rust_toolchain): The current target's `rust_toolchain`
722 tool_path (str): Path to rustc
723 cc_toolchain (CcToolchain): The CcToolchain for the current target.
724 feature_configuration (FeatureConfiguration): Class used to construct command lines from CROSSTOOL features.
725 crate_info (CrateInfo): The CrateInfo provider of the target crate
726 dep_info (DepInfo): The DepInfo provider of the target crate
727 linkstamp_outs (list): Linkstamp outputs of native dependencies
728 ambiguous_libs (dict): Ambiguous libs, see `_disambiguate_libs`
729 output_hash (str): The hashed path of the crate root
730 rust_flags (list): Additional flags to pass to rustc
731 out_dir (str): The path to the output directory for the target Crate.
732 build_env_files (list): Files containing rustc environment variables, for instance from `cargo_build_script` actions.
733 build_flags_files (depset): The output files of a `cargo_build_script` actions containing rustc build flags
734 emit (list): Values for the --emit flag to rustc.
735 force_all_deps_direct (bool, optional): Whether to pass the transitive rlibs with --extern
736 to the commandline as opposed to -L.
737 force_link (bool, optional): Whether to add link flags to the command regardless of `emit`.
738 stamp (bool, optional): Whether or not workspace status stamping is enabled. For more details see
739 https://docs.bazel.build/versions/main/user-manual.html#flag--stamp
740 remap_path_prefix (str, optional): A value used to remap `${pwd}` to. If set to a falsey value, no prefix will be set.
Brian Silverman5f6f2762022-08-13 19:30:05 -0700741 use_json_output (bool): Have rustc emit json and process_wrapper parse json messages to output rendered output.
742 build_metadata (bool): Generate CLI arguments for building *only* .rmeta files. This requires use_json_output.
743 force_depend_on_objects (bool): Force using `.rlib` object files instead of metadata (`.rmeta`) files even if they are available.
Brian Silvermancc09f182022-03-09 15:40:20 -0800744
745 Returns:
746 tuple: A tuple of the following items
747 - (struct): A struct of arguments used to run the `Rustc` action
748 - process_wrapper_flags (Args): Arguments for the process wrapper
749 - rustc_path (Args): Arguments for invoking rustc via the process wrapper
750 - rustc_flags (Args): Rust flags for the Rust compiler
751 - all (list): A list of all `Args` objects in the order listed above.
752 This is to be passed to the `arguments` parameter of actions
753 - (dict): Common rustc environment variables
754 """
Brian Silverman5f6f2762022-08-13 19:30:05 -0700755 if build_metadata and not use_json_output:
756 fail("build_metadata requires parse_json_output")
757
Brian Silvermancc09f182022-03-09 15:40:20 -0800758 output_dir = getattr(crate_info.output, "dirname", None)
759 linker_script = getattr(file, "linker_script", None)
760
761 env = _get_rustc_env(attr, toolchain, crate_info.name)
762
763 # Wrapper args first
764 process_wrapper_flags = ctx.actions.args()
765
766 for build_env_file in build_env_files:
767 process_wrapper_flags.add("--env-file", build_env_file)
768
769 process_wrapper_flags.add_all(build_flags_files, before_each = "--arg-file")
770
771 # Certain rust build processes expect to find files from the environment
772 # variable `$CARGO_MANIFEST_DIR`. Examples of this include pest, tera,
773 # asakuma.
774 #
775 # The compiler and by extension proc-macros see the current working
776 # directory as the Bazel exec root. This is what `$CARGO_MANIFEST_DIR`
777 # would default to but is often the wrong value (e.g. if the source is in a
778 # sub-package or if we are building something in an external repository).
779 # Hence, we need to set `CARGO_MANIFEST_DIR` explicitly.
780 #
781 # Since we cannot get the `exec_root` from starlark, we cheat a little and
782 # use `${pwd}` which resolves the `exec_root` at action execution time.
783 process_wrapper_flags.add("--subst", "pwd=${pwd}")
784
785 # If stamping is enabled, enable the functionality in the process wrapper
786 if stamp:
787 process_wrapper_flags.add("--volatile-status-file", ctx.version_file)
Brian Silverman5f6f2762022-08-13 19:30:05 -0700788 process_wrapper_flags.add("--stable-status-file", ctx.info_file)
Brian Silvermancc09f182022-03-09 15:40:20 -0800789
790 # Both ctx.label.workspace_root and ctx.label.package are relative paths
791 # and either can be empty strings. Avoid trailing/double slashes in the path.
792 components = "${{pwd}}/{}/{}".format(ctx.label.workspace_root, ctx.label.package).split("/")
793 env["CARGO_MANIFEST_DIR"] = "/".join([c for c in components if c])
794
795 if out_dir != None:
796 env["OUT_DIR"] = "${pwd}/" + out_dir
797
798 # Handle that the binary name and crate name may be different.
799 #
800 # If a target name contains a - then cargo (and rules_rust) will generate a
801 # crate name with _ instead. Accordingly, rustc will generate a output
802 # file (executable, or rlib, or whatever) with _ not -. But when cargo
803 # puts a binary in the target/${config} directory, and sets environment
804 # variables like `CARGO_BIN_EXE_${binary_name}` it will use the - version
805 # not the _ version. So we rename the rustc-generated file (with _s) to
806 # have -s if needed.
807 emit_with_paths = emit
808 if crate_info.type == "bin" and crate_info.output != None:
809 generated_file = crate_info.name + toolchain.binary_ext
810 src = "/".join([crate_info.output.dirname, generated_file])
811 dst = crate_info.output.path
812 if src != dst:
813 emit_with_paths = [("link=" + dst if val == "link" else val) for val in emit]
814
815 # Arguments for launching rustc from the process wrapper
816 rustc_path = ctx.actions.args()
817 rustc_path.add("--")
818 rustc_path.add(tool_path)
819
820 # Rustc arguments
821 rustc_flags = ctx.actions.args()
822 rustc_flags.set_param_file_format("multiline")
823 rustc_flags.use_param_file("@%s", use_always = False)
824 rustc_flags.add(crate_info.root)
825 rustc_flags.add("--crate-name=" + crate_info.name)
826 rustc_flags.add("--crate-type=" + crate_info.type)
Brian Silvermancc09f182022-03-09 15:40:20 -0800827
Brian Silverman5f6f2762022-08-13 19:30:05 -0700828 error_format = "human"
829 if hasattr(attr, "_error_format"):
830 error_format = attr._error_format[ErrorFormatInfo].error_format
831
832 if use_json_output:
833 # If --error-format was set to json, we just pass the output through
834 # Otherwise process_wrapper uses the "rendered" field.
835 process_wrapper_flags.add("--rustc-output-format", "json" if error_format == "json" else "rendered")
836
837 # Configure rustc json output by adding artifact notifications.
838 # These will always be filtered out by process_wrapper and will be use to terminate
839 # rustc when appropriate.
840 json = ["artifacts"]
841 if error_format == "short":
842 json.append("diagnostic-short")
843 elif error_format == "human" and toolchain.os != "windows":
844 # If the os is not windows, we can get colorized output.
845 json.append("diagnostic-rendered-ansi")
846
847 rustc_flags.add("--json=" + ",".join(json))
848
849 error_format = "json"
850
851 if build_metadata:
852 # Configure process_wrapper to terminate rustc when metadata are emitted
853 process_wrapper_flags.add("--rustc-quit-on-rmeta", "true")
854
855 rustc_flags.add("--error-format=" + error_format)
856
857 # Mangle symbols to disambiguate crates with the same name. This could
858 # happen only for non-final artifacts where we compute an output_hash,
859 # e.g., rust_library.
860 #
861 # For "final" artifacts and ones intended for distribution outside of
862 # Bazel, such as rust_binary, rust_static_library and rust_shared_library,
863 # where output_hash is None we don't need to add these flags.
864 if output_hash:
865 extra_filename = "-" + output_hash
866 rustc_flags.add("--codegen=metadata=" + extra_filename)
867 rustc_flags.add("--codegen=extra-filename=" + extra_filename)
868
Brian Silvermancc09f182022-03-09 15:40:20 -0800869 if output_dir:
870 rustc_flags.add("--out-dir=" + output_dir)
Brian Silvermancc09f182022-03-09 15:40:20 -0800871
872 compilation_mode = get_compilation_mode_opts(ctx, toolchain)
873 rustc_flags.add("--codegen=opt-level=" + compilation_mode.opt_level)
874 rustc_flags.add("--codegen=debuginfo=" + compilation_mode.debug_info)
875
876 # For determinism to help with build distribution and such
877 if remap_path_prefix:
878 rustc_flags.add("--remap-path-prefix=${{pwd}}={}".format(remap_path_prefix))
879
880 if emit:
881 rustc_flags.add("--emit=" + ",".join(emit_with_paths))
Brian Silverman5f6f2762022-08-13 19:30:05 -0700882 if error_format != "json":
883 # Color is not compatible with json output.
884 rustc_flags.add("--color=always")
Brian Silvermancc09f182022-03-09 15:40:20 -0800885 rustc_flags.add("--target=" + toolchain.target_flag_value)
886 if hasattr(attr, "crate_features"):
887 rustc_flags.add_all(getattr(attr, "crate_features"), before_each = "--cfg", format_each = 'feature="%s"')
888 if linker_script:
889 rustc_flags.add(linker_script.path, format = "--codegen=link-arg=-T%s")
890
891 # Gets the paths to the folders containing the standard library (or libcore)
892 rust_std_paths = toolchain.rust_std_paths.to_list()
893
894 # Tell Rustc where to find the standard library
895 rustc_flags.add_all(rust_std_paths, before_each = "-L", format_each = "%s")
896 rustc_flags.add_all(rust_flags)
897
898 # Deduplicate data paths due to https://github.com/bazelbuild/bazel/issues/14681
899 data_paths = depset(direct = getattr(attr, "data", []) + getattr(attr, "compile_data", [])).to_list()
900
901 rustc_flags.add_all(
902 expand_list_element_locations(
903 ctx,
904 getattr(attr, "rustc_flags", []),
905 data_paths,
906 ),
907 )
908 add_edition_flags(rustc_flags, crate_info)
909
910 # Link!
911 if ("link" in emit and crate_info.type not in ["rlib", "lib"]) or force_link:
912 # Rust's built-in linker can handle linking wasm files. We don't want to attempt to use the cc
913 # linker since it won't understand.
914 if toolchain.target_arch != "wasm32":
915 if output_dir:
916 use_pic = _should_use_pic(cc_toolchain, feature_configuration, crate_info.type)
917 rpaths = _compute_rpaths(toolchain, output_dir, dep_info, use_pic)
918 else:
919 rpaths = depset([])
920 ld, link_args, link_env = get_linker_and_args(ctx, attr, cc_toolchain, feature_configuration, rpaths)
921 env.update(link_env)
922 rustc_flags.add("--codegen=linker=" + ld)
923 rustc_flags.add_joined("--codegen", link_args, join_with = " ", format_joined = "link-args=%s")
924
925 _add_native_link_flags(rustc_flags, dep_info, linkstamp_outs, ambiguous_libs, crate_info.type, toolchain, cc_toolchain, feature_configuration)
926
Brian Silverman5f6f2762022-08-13 19:30:05 -0700927 use_metadata = _depend_on_metadata(crate_info, force_depend_on_objects)
Brian Silvermancc09f182022-03-09 15:40:20 -0800928
Brian Silverman5f6f2762022-08-13 19:30:05 -0700929 # These always need to be added, even if not linking this crate.
930 add_crate_link_flags(rustc_flags, dep_info, force_all_deps_direct, use_metadata)
931
932 needs_extern_proc_macro_flag = _is_proc_macro(crate_info) and crate_info.edition != "2015"
Brian Silvermancc09f182022-03-09 15:40:20 -0800933 if needs_extern_proc_macro_flag:
934 rustc_flags.add("--extern")
935 rustc_flags.add("proc_macro")
936
Brian Silverman5f6f2762022-08-13 19:30:05 -0700937 if toolchain.llvm_cov and ctx.configuration.coverage_enabled:
938 rustc_flags.add("--codegen=instrument-coverage")
939
Brian Silvermancc09f182022-03-09 15:40:20 -0800940 # Make bin crate data deps available to tests.
941 for data in getattr(attr, "data", []):
942 if rust_common.crate_info in data:
943 dep_crate_info = data[rust_common.crate_info]
944 if dep_crate_info.type == "bin":
945 # Trying to make CARGO_BIN_EXE_{} canonical across platform by strip out extension if exists
946 env_basename = dep_crate_info.output.basename[:-(1 + len(dep_crate_info.output.extension))] if len(dep_crate_info.output.extension) > 0 else dep_crate_info.output.basename
947 env["CARGO_BIN_EXE_" + env_basename] = dep_crate_info.output.short_path
948
Brian Silverman5f6f2762022-08-13 19:30:05 -0700949 # Add environment variables from the Rust toolchain.
950 env.update(toolchain.env)
951
Brian Silvermancc09f182022-03-09 15:40:20 -0800952 # Update environment with user provided variables.
953 env.update(expand_dict_value_locations(
954 ctx,
955 crate_info.rustc_env,
956 data_paths,
957 ))
958
959 # Ensure the sysroot is set for the target platform
960 env["SYSROOT"] = toolchain.sysroot
961
962 if toolchain._rename_first_party_crates:
963 env["RULES_RUST_THIRD_PARTY_DIR"] = toolchain._third_party_dir
964
965 # extra_rustc_flags apply to the target configuration, not the exec configuration.
966 if hasattr(ctx.attr, "_extra_rustc_flags") and not is_exec_configuration(ctx):
967 rustc_flags.add_all(ctx.attr._extra_rustc_flags[ExtraRustcFlagsInfo].extra_rustc_flags)
968
Brian Silverman5f6f2762022-08-13 19:30:05 -0700969 if hasattr(ctx.attr, "_extra_rustc_flag") and not is_exec_configuration(ctx):
970 rustc_flags.add_all(ctx.attr._extra_rustc_flag[ExtraRustcFlagsInfo].extra_rustc_flags)
971
Brian Silvermancc09f182022-03-09 15:40:20 -0800972 if hasattr(ctx.attr, "_extra_exec_rustc_flags") and is_exec_configuration(ctx):
973 rustc_flags.add_all(ctx.attr._extra_exec_rustc_flags[ExtraExecRustcFlagsInfo].extra_exec_rustc_flags)
974
Brian Silverman5f6f2762022-08-13 19:30:05 -0700975 if hasattr(ctx.attr, "_extra_exec_rustc_flag") and is_exec_configuration(ctx):
976 rustc_flags.add_all(ctx.attr._extra_exec_rustc_flag[ExtraExecRustcFlagsInfo].extra_exec_rustc_flags)
977
Brian Silvermancc09f182022-03-09 15:40:20 -0800978 # Create a struct which keeps the arguments separate so each may be tuned or
979 # replaced where necessary
980 args = struct(
981 process_wrapper_flags = process_wrapper_flags,
982 rustc_path = rustc_path,
983 rustc_flags = rustc_flags,
984 all = [process_wrapper_flags, rustc_path, rustc_flags],
985 )
986
987 return args, env
988
989def rustc_compile_action(
990 ctx,
991 attr,
992 toolchain,
993 crate_info,
994 output_hash = None,
995 rust_flags = [],
996 force_all_deps_direct = False):
997 """Create and run a rustc compile action based on the current rule's attributes
998
999 Args:
1000 ctx (ctx): The rule's context object
1001 attr (struct): Attributes to use for the rust compile action
1002 toolchain (rust_toolchain): The current `rust_toolchain`
1003 crate_info (CrateInfo): The CrateInfo provider for the current target.
1004 output_hash (str, optional): The hashed path of the crate root. Defaults to None.
1005 rust_flags (list, optional): Additional flags to pass to rustc. Defaults to [].
1006 force_all_deps_direct (bool, optional): Whether to pass the transitive rlibs with --extern
1007 to the commandline as opposed to -L.
1008
1009 Returns:
1010 list: A list of the following providers:
1011 - (CrateInfo): info for the crate we just built; same as `crate_info` parameter.
1012 - (DepInfo): The transitive dependencies of this crate.
1013 - (DefaultInfo): The output file for this crate, and its runfiles.
1014 """
Brian Silverman5f6f2762022-08-13 19:30:05 -07001015 build_metadata = getattr(crate_info, "metadata", None)
1016
Brian Silvermancc09f182022-03-09 15:40:20 -08001017 cc_toolchain, feature_configuration = find_cc_toolchain(ctx)
1018
Brian Silverman5f6f2762022-08-13 19:30:05 -07001019 # Determine whether to use cc_common.link:
1020 # * either if experimental_use_cc_common_link is 1,
1021 # * or if experimental_use_cc_common_link is -1 and
1022 # the toolchain experimental_use_cc_common_link is true.
1023 experimental_use_cc_common_link = False
1024 if hasattr(ctx.attr, "experimental_use_cc_common_link"):
1025 if ctx.attr.experimental_use_cc_common_link == 0:
1026 experimental_use_cc_common_link = False
1027 elif ctx.attr.experimental_use_cc_common_link == 1:
1028 experimental_use_cc_common_link = True
1029 elif ctx.attr.experimental_use_cc_common_link == -1:
1030 experimental_use_cc_common_link = toolchain._experimental_use_cc_common_link
1031
Brian Silvermancc09f182022-03-09 15:40:20 -08001032 dep_info, build_info, linkstamps = collect_deps(
1033 deps = crate_info.deps,
1034 proc_macro_deps = crate_info.proc_macro_deps,
1035 aliases = crate_info.aliases,
1036 are_linkstamps_supported = _are_linkstamps_supported(
1037 feature_configuration = feature_configuration,
1038 has_grep_includes = hasattr(ctx.attr, "_grep_includes"),
1039 ),
1040 )
1041
1042 # Determine if the build is currently running with --stamp
1043 stamp = is_stamping_enabled(attr)
1044
1045 compile_inputs, out_dir, build_env_files, build_flags_files, linkstamp_outs, ambiguous_libs = collect_inputs(
1046 ctx = ctx,
1047 file = ctx.file,
1048 files = ctx.files,
1049 linkstamps = linkstamps,
1050 toolchain = toolchain,
1051 cc_toolchain = cc_toolchain,
1052 feature_configuration = feature_configuration,
1053 crate_info = crate_info,
1054 dep_info = dep_info,
1055 build_info = build_info,
1056 stamp = stamp,
Brian Silverman5f6f2762022-08-13 19:30:05 -07001057 experimental_use_cc_common_link = experimental_use_cc_common_link,
Brian Silvermancc09f182022-03-09 15:40:20 -08001058 )
1059
Brian Silverman5f6f2762022-08-13 19:30:05 -07001060 # The types of rustc outputs to emit.
1061 # If we build metadata, we need to keep the command line of the two invocations
1062 # (rlib and rmeta) as similar as possible, otherwise rustc rejects the rmeta as
1063 # a candidate.
1064 # Because of that we need to add emit=metadata to both the rlib and rmeta invocation.
1065 #
1066 # When cc_common linking is enabled, emit a `.o` file, which is later
1067 # passed to the cc_common.link action.
1068 emit = ["dep-info", "link"]
1069 if build_metadata:
1070 emit.append("metadata")
1071 if experimental_use_cc_common_link:
1072 emit = ["obj"]
1073
Brian Silvermancc09f182022-03-09 15:40:20 -08001074 args, env_from_args = construct_arguments(
1075 ctx = ctx,
1076 attr = attr,
1077 file = ctx.file,
1078 toolchain = toolchain,
1079 tool_path = toolchain.rustc.path,
1080 cc_toolchain = cc_toolchain,
Brian Silverman5f6f2762022-08-13 19:30:05 -07001081 emit = emit,
Brian Silvermancc09f182022-03-09 15:40:20 -08001082 feature_configuration = feature_configuration,
1083 crate_info = crate_info,
1084 dep_info = dep_info,
1085 linkstamp_outs = linkstamp_outs,
1086 ambiguous_libs = ambiguous_libs,
1087 output_hash = output_hash,
1088 rust_flags = rust_flags,
1089 out_dir = out_dir,
1090 build_env_files = build_env_files,
1091 build_flags_files = build_flags_files,
1092 force_all_deps_direct = force_all_deps_direct,
1093 stamp = stamp,
Brian Silverman5f6f2762022-08-13 19:30:05 -07001094 use_json_output = bool(build_metadata),
Brian Silvermancc09f182022-03-09 15:40:20 -08001095 )
1096
Brian Silverman5f6f2762022-08-13 19:30:05 -07001097 args_metadata = None
1098 if build_metadata:
1099 args_metadata, _ = construct_arguments(
1100 ctx = ctx,
1101 attr = attr,
1102 file = ctx.file,
1103 toolchain = toolchain,
1104 tool_path = toolchain.rustc.path,
1105 cc_toolchain = cc_toolchain,
1106 emit = emit,
1107 feature_configuration = feature_configuration,
1108 crate_info = crate_info,
1109 dep_info = dep_info,
1110 linkstamp_outs = linkstamp_outs,
1111 ambiguous_libs = ambiguous_libs,
1112 output_hash = output_hash,
1113 rust_flags = rust_flags,
1114 out_dir = out_dir,
1115 build_env_files = build_env_files,
1116 build_flags_files = build_flags_files,
1117 force_all_deps_direct = force_all_deps_direct,
1118 stamp = stamp,
1119 use_json_output = True,
1120 build_metadata = True,
1121 )
1122
Brian Silvermancc09f182022-03-09 15:40:20 -08001123 env = dict(ctx.configuration.default_shell_env)
1124 env.update(env_from_args)
1125
1126 if hasattr(attr, "version") and attr.version != "0.0.0":
1127 formatted_version = " v{}".format(attr.version)
1128 else:
1129 formatted_version = ""
1130
Brian Silverman5f6f2762022-08-13 19:30:05 -07001131 # Declares the outputs of the rustc compile action.
1132 # By default this is the binary output; if cc_common.link is used, this is
1133 # the main `.o` file (`output_o` below).
Brian Silvermancc09f182022-03-09 15:40:20 -08001134 outputs = [crate_info.output]
1135
Brian Silverman5f6f2762022-08-13 19:30:05 -07001136 # The `.o` output file, only used for linking via cc_common.link.
1137 output_o = None
1138 if experimental_use_cc_common_link:
1139 obj_ext = ".o"
1140 output_o = ctx.actions.declare_file(crate_info.name + obj_ext, sibling = crate_info.output)
1141 outputs = [output_o]
1142
Brian Silvermancc09f182022-03-09 15:40:20 -08001143 # For a cdylib that might be added as a dependency to a cc_* target on Windows, it is important to include the
1144 # interface library that rustc generates in the output files.
1145 interface_library = None
1146 if toolchain.os == "windows" and crate_info.type == "cdylib":
1147 # Rustc generates the import library with a `.dll.lib` extension rather than the usual `.lib` one that msvc
1148 # expects (see https://github.com/rust-lang/rust/pull/29520 for more context).
Brian Silverman5f6f2762022-08-13 19:30:05 -07001149 interface_library = ctx.actions.declare_file(crate_info.output.basename + ".lib", sibling = crate_info.output)
Brian Silvermancc09f182022-03-09 15:40:20 -08001150 outputs.append(interface_library)
1151
1152 # The action might generate extra output that we don't want to include in the `DefaultInfo` files.
1153 action_outputs = list(outputs)
1154
1155 # Rustc generates a pdb file (on Windows) or a dsym folder (on macos) so provide it in an output group for crate
1156 # types that benefit from having debug information in a separate file.
1157 pdb_file = None
1158 dsym_folder = None
1159 if crate_info.type in ("cdylib", "bin") and not crate_info.is_test:
1160 if toolchain.os == "windows":
Brian Silverman5f6f2762022-08-13 19:30:05 -07001161 pdb_file = ctx.actions.declare_file(crate_info.output.basename[:-len(crate_info.output.extension)] + "pdb", sibling = crate_info.output)
Brian Silvermancc09f182022-03-09 15:40:20 -08001162 action_outputs.append(pdb_file)
1163 elif toolchain.os == "darwin":
Brian Silverman5f6f2762022-08-13 19:30:05 -07001164 dsym_folder = ctx.actions.declare_directory(crate_info.output.basename + ".dSYM", sibling = crate_info.output)
Brian Silvermancc09f182022-03-09 15:40:20 -08001165 action_outputs.append(dsym_folder)
1166
Brian Silverman5f6f2762022-08-13 19:30:05 -07001167 if ctx.executable._process_wrapper:
Brian Silvermancc09f182022-03-09 15:40:20 -08001168 # Run as normal
1169 ctx.actions.run(
1170 executable = ctx.executable._process_wrapper,
1171 inputs = compile_inputs,
1172 outputs = action_outputs,
1173 env = env,
1174 arguments = args.all,
1175 mnemonic = "Rustc",
1176 progress_message = "Compiling Rust {} {}{} ({} files)".format(
1177 crate_info.type,
1178 ctx.label.name,
1179 formatted_version,
1180 len(crate_info.srcs.to_list()),
1181 ),
1182 )
Brian Silverman5f6f2762022-08-13 19:30:05 -07001183 if args_metadata:
1184 ctx.actions.run(
1185 executable = ctx.executable._process_wrapper,
1186 inputs = compile_inputs,
1187 outputs = [build_metadata],
1188 env = env,
1189 arguments = args_metadata.all,
1190 mnemonic = "RustcMetadata",
1191 progress_message = "Compiling Rust metadata {} {}{} ({} files)".format(
1192 crate_info.type,
1193 ctx.label.name,
1194 formatted_version,
1195 len(crate_info.srcs.to_list()),
1196 ),
1197 )
Brian Silvermancc09f182022-03-09 15:40:20 -08001198 else:
1199 # Run without process_wrapper
Brian Silverman5f6f2762022-08-13 19:30:05 -07001200 if build_env_files or build_flags_files or stamp or build_metadata:
1201 fail("build_env_files, build_flags_files, stamp, build_metadata are not supported when building without process_wrapper")
Brian Silvermancc09f182022-03-09 15:40:20 -08001202 ctx.actions.run(
1203 executable = toolchain.rustc,
1204 inputs = compile_inputs,
1205 outputs = action_outputs,
1206 env = env,
1207 arguments = [args.rustc_flags],
1208 mnemonic = "Rustc",
1209 progress_message = "Compiling Rust (without process_wrapper) {} {}{} ({} files)".format(
1210 crate_info.type,
1211 ctx.label.name,
1212 formatted_version,
1213 len(crate_info.srcs.to_list()),
1214 ),
1215 )
1216
Brian Silverman5f6f2762022-08-13 19:30:05 -07001217 if experimental_use_cc_common_link:
1218 # Wrap the main `.o` file into a compilation output suitable for
1219 # cc_common.link. The main `.o` file is useful in both PIC and non-PIC
1220 # modes.
1221 compilation_outputs = cc_common.create_compilation_outputs(
1222 objects = depset([output_o]),
1223 pic_objects = depset([output_o]),
1224 )
1225
1226 # Collect the linking contexts of the standard library and dependencies.
1227 linking_contexts = [toolchain.libstd_and_allocator_ccinfo.linking_context, toolchain.stdlib_linkflags.linking_context]
1228
1229 for dep in crate_info.deps.to_list():
1230 if dep.cc_info:
1231 linking_contexts.append(dep.cc_info.linking_context)
1232
1233 # In the cc_common.link action we need to pass the name of the final
1234 # binary (output) relative to the package of this target.
1235 # We compute it by stripping the path to the package directory,
1236 # which is a prefix of the path of `crate_info.output`.
1237
1238 # The path to the package dir, including a trailing "/".
1239 package_dir = ctx.bin_dir.path + "/"
1240 if ctx.label.workspace_root:
1241 package_dir = package_dir + ctx.label.workspace_root + "/"
1242 if ctx.label.package:
1243 package_dir = package_dir + ctx.label.package + "/"
1244
1245 if not crate_info.output.path.startswith(package_dir):
1246 fail("The package dir path {} should be a prefix of the crate_info.output.path {}", package_dir, crate_info.output.path)
1247
1248 output_relative_to_package = crate_info.output.path[len(package_dir):]
1249
1250 cc_common.link(
1251 actions = ctx.actions,
1252 feature_configuration = feature_configuration,
1253 cc_toolchain = cc_toolchain,
1254 linking_contexts = linking_contexts,
1255 compilation_outputs = compilation_outputs,
1256 name = output_relative_to_package,
1257 grep_includes = ctx.file._grep_includes,
1258 stamp = ctx.attr.stamp,
1259 )
1260
1261 outputs = [crate_info.output]
1262
1263 coverage_runfiles = []
1264 if toolchain.llvm_cov and ctx.configuration.coverage_enabled and crate_info.is_test:
1265 coverage_runfiles = [toolchain.llvm_cov, toolchain.llvm_profdata]
1266
Brian Silvermancc09f182022-03-09 15:40:20 -08001267 runfiles = ctx.runfiles(
Brian Silverman5f6f2762022-08-13 19:30:05 -07001268 files = getattr(ctx.files, "data", []) + coverage_runfiles,
Brian Silvermancc09f182022-03-09 15:40:20 -08001269 collect_data = True,
1270 )
1271
1272 # TODO: Remove after some resolution to
1273 # https://github.com/bazelbuild/rules_rust/issues/771
1274 out_binary = getattr(attr, "out_binary", False)
1275
1276 providers = [
Brian Silvermancc09f182022-03-09 15:40:20 -08001277 DefaultInfo(
1278 # nb. This field is required for cc_library to depend on our output.
1279 files = depset(outputs),
1280 runfiles = runfiles,
1281 executable = crate_info.output if crate_info.type == "bin" or crate_info.is_test or out_binary else None,
1282 ),
Brian Silverman5f6f2762022-08-13 19:30:05 -07001283 coverage_common.instrumented_files_info(
1284 ctx,
1285 dependency_attributes = ["deps", "crate"],
1286 extensions = ["rs"],
1287 source_attributes = ["srcs"],
1288 ),
Brian Silvermancc09f182022-03-09 15:40:20 -08001289 ]
Brian Silverman5f6f2762022-08-13 19:30:05 -07001290
1291 if crate_info.type in ["staticlib", "cdylib"]:
1292 # These rules are not supposed to be depended on by other rust targets, and
1293 # as such they shouldn't provide a CrateInfo. However, one may still want to
1294 # write a rust_test for them, so we provide the CrateInfo wrapped in a provider
1295 # that rust_test understands.
1296 providers.extend([rust_common.test_crate_info(crate = crate_info), dep_info])
1297 else:
1298 providers.extend([crate_info, dep_info])
1299
Brian Silvermancc09f182022-03-09 15:40:20 -08001300 if toolchain.target_arch != "wasm32":
1301 providers += establish_cc_info(ctx, attr, crate_info, toolchain, cc_toolchain, feature_configuration, interface_library)
1302 if pdb_file:
1303 providers.append(OutputGroupInfo(pdb_file = depset([pdb_file])))
1304 if dsym_folder:
1305 providers.append(OutputGroupInfo(dsym_folder = depset([dsym_folder])))
1306
1307 return providers
1308
1309def _is_dylib(dep):
1310 return not bool(dep.static_library or dep.pic_static_library)
1311
Brian Silverman5f6f2762022-08-13 19:30:05 -07001312def _collect_nonstatic_linker_inputs(cc_info):
1313 shared_linker_inputs = []
1314 for linker_input in cc_info.linking_context.linker_inputs.to_list():
1315 dylibs = [
1316 lib
1317 for lib in linker_input.libraries
1318 if _is_dylib(lib)
1319 ]
1320 if dylibs:
1321 shared_linker_inputs.append(cc_common.create_linker_input(
1322 owner = linker_input.owner,
1323 libraries = depset(dylibs),
1324 ))
1325 return shared_linker_inputs
1326
Brian Silvermancc09f182022-03-09 15:40:20 -08001327def establish_cc_info(ctx, attr, crate_info, toolchain, cc_toolchain, feature_configuration, interface_library):
1328 """If the produced crate is suitable yield a CcInfo to allow for interop with cc rules
1329
1330 Args:
1331 ctx (ctx): The rule's context object
1332 attr (struct): Attributes to use in gathering CcInfo
1333 crate_info (CrateInfo): The CrateInfo provider of the target crate
1334 toolchain (rust_toolchain): The current `rust_toolchain`
1335 cc_toolchain (CcToolchainInfo): The current `CcToolchainInfo`
1336 feature_configuration (FeatureConfiguration): Feature configuration to be queried.
1337 interface_library (File): Optional interface library for cdylib crates on Windows.
1338
1339 Returns:
1340 list: A list containing the CcInfo provider
1341 """
1342
1343 # A test will not need to produce CcInfo as nothing can depend on test targets
1344 if crate_info.is_test:
1345 return []
1346
1347 # Only generate CcInfo for particular crate types
1348 if crate_info.type not in ("staticlib", "cdylib", "rlib", "lib"):
1349 return []
1350
1351 # TODO: Remove after some resolution to
1352 # https://github.com/bazelbuild/rules_rust/issues/771
1353 if getattr(attr, "out_binary", False):
1354 return []
1355
1356 if crate_info.type == "staticlib":
1357 library_to_link = cc_common.create_library_to_link(
1358 actions = ctx.actions,
1359 feature_configuration = feature_configuration,
1360 cc_toolchain = cc_toolchain,
1361 static_library = crate_info.output,
1362 # TODO(hlopko): handle PIC/NOPIC correctly
1363 pic_static_library = crate_info.output,
1364 )
1365 elif crate_info.type in ("rlib", "lib"):
1366 # bazel hard-codes a check for endswith((".a", ".pic.a",
1367 # ".lib")) in create_library_to_link, so we work around that
1368 # by creating a symlink to the .rlib with a .a extension.
1369 dot_a = make_static_lib_symlink(ctx.actions, crate_info.output)
1370
1371 # TODO(hlopko): handle PIC/NOPIC correctly
1372 library_to_link = cc_common.create_library_to_link(
1373 actions = ctx.actions,
1374 feature_configuration = feature_configuration,
1375 cc_toolchain = cc_toolchain,
1376 static_library = dot_a,
1377 # TODO(hlopko): handle PIC/NOPIC correctly
1378 pic_static_library = dot_a,
1379 )
1380 elif crate_info.type == "cdylib":
1381 library_to_link = cc_common.create_library_to_link(
1382 actions = ctx.actions,
1383 feature_configuration = feature_configuration,
1384 cc_toolchain = cc_toolchain,
1385 dynamic_library = crate_info.output,
1386 interface_library = interface_library,
1387 )
1388 else:
1389 fail("Unexpected case")
1390
1391 link_input = cc_common.create_linker_input(
1392 owner = ctx.label,
1393 libraries = depset([library_to_link]),
1394 )
1395
1396 linking_context = cc_common.create_linking_context(
1397 # TODO - What to do for no_std?
1398 linker_inputs = depset([link_input]),
1399 )
1400
1401 cc_infos = [
1402 CcInfo(linking_context = linking_context),
1403 toolchain.stdlib_linkflags,
1404 ]
Brian Silverman5f6f2762022-08-13 19:30:05 -07001405
Brian Silvermancc09f182022-03-09 15:40:20 -08001406 for dep in getattr(attr, "deps", []):
1407 if CcInfo in dep:
Brian Silverman5f6f2762022-08-13 19:30:05 -07001408 # A Rust staticlib or shared library doesn't need to propagate linker inputs
1409 # of its dependencies, except for shared libraries.
1410 if crate_info.type in ["cdylib", "staticlib"]:
1411 shared_linker_inputs = _collect_nonstatic_linker_inputs(dep[CcInfo])
1412 if shared_linker_inputs:
1413 linking_context = cc_common.create_linking_context(
1414 linker_inputs = depset(shared_linker_inputs),
1415 )
1416 cc_infos.append(CcInfo(linking_context = linking_context))
1417 else:
1418 cc_infos.append(dep[CcInfo])
Brian Silvermancc09f182022-03-09 15:40:20 -08001419
1420 if crate_info.type in ("rlib", "lib") and toolchain.libstd_and_allocator_ccinfo:
1421 # TODO: if we already have an rlib in our deps, we could skip this
1422 cc_infos.append(toolchain.libstd_and_allocator_ccinfo)
1423
1424 return [cc_common.merge_cc_infos(cc_infos = cc_infos)]
1425
1426def add_edition_flags(args, crate):
1427 """Adds the Rust edition flag to an arguments object reference
1428
1429 Args:
1430 args (Args): A reference to an Args object
1431 crate (CrateInfo): A CrateInfo provider
1432 """
1433 if crate.edition != "2015":
1434 args.add("--edition={}".format(crate.edition))
1435
1436def _create_extra_input_args(build_info, dep_info):
1437 """Gather additional input arguments from transitive dependencies
1438
1439 Args:
1440 build_info (BuildInfo): The BuildInfo provider from the target Crate's set of inputs.
1441 dep_info (DepInfo): The Depinfo provider form the target Crate's set of inputs.
1442
1443 Returns:
1444 tuple: A tuple of the following items:
1445 - (depset[File]): A list of all build info `OUT_DIR` File objects
1446 - (str): The `OUT_DIR` of the current build info
1447 - (File): An optional generated environment file from a `cargo_build_script` target
1448 - (depset[File]): All direct and transitive build flag files from the current build info.
1449 """
1450 input_files = []
1451
1452 # Arguments to the commandline line wrapper that are going to be used
1453 # to create the final command line
1454 out_dir = None
1455 build_env_file = None
1456 build_flags_files = []
1457
1458 if build_info:
1459 out_dir = build_info.out_dir.path
1460 build_env_file = build_info.rustc_env
1461 build_flags_files.append(build_info.flags)
1462 build_flags_files.append(build_info.link_flags)
1463 input_files.append(build_info.out_dir)
1464 input_files.append(build_info.link_flags)
1465
1466 return (
1467 depset(input_files, transitive = [dep_info.link_search_path_files]),
1468 out_dir,
1469 build_env_file,
1470 depset(build_flags_files, transitive = [dep_info.link_search_path_files]),
1471 )
1472
1473def _compute_rpaths(toolchain, output_dir, dep_info, use_pic):
1474 """Determine the artifact's rpaths relative to the bazel root for runtime linking of shared libraries.
1475
1476 Args:
1477 toolchain (rust_toolchain): The current `rust_toolchain`
1478 output_dir (str): The output directory of the current target
1479 dep_info (DepInfo): The current target's dependency info
1480 use_pic: If set, prefers pic_static_library over static_library.
1481
1482 Returns:
1483 depset: A set of relative paths from the output directory to each dependency
1484 """
1485
1486 # Windows has no rpath equivalent, so always return an empty depset.
Brian Silverman5f6f2762022-08-13 19:30:05 -07001487 # Fuchsia assembles shared libraries during packaging.
1488 if toolchain.os == "windows" or toolchain.os == "fuchsia":
Brian Silvermancc09f182022-03-09 15:40:20 -08001489 return depset([])
1490
1491 dylibs = [
1492 get_preferred_artifact(lib, use_pic)
1493 for linker_input in dep_info.transitive_noncrates.to_list()
1494 for lib in linker_input.libraries
1495 if _is_dylib(lib)
1496 ]
1497 if not dylibs:
1498 return depset([])
1499
1500 # For darwin, dylibs compiled by Bazel will fail to be resolved at runtime
1501 # without a version of Bazel that includes
1502 # https://github.com/bazelbuild/bazel/pull/13427. This is known to not be
1503 # included in Bazel 4.1 and below.
1504 if toolchain.os != "linux" and toolchain.os != "darwin":
1505 fail("Runtime linking is not supported on {}, but found {}".format(
1506 toolchain.os,
1507 dep_info.transitive_noncrates,
1508 ))
1509
1510 # Multiple dylibs can be present in the same directory, so deduplicate them.
1511 return depset([
1512 relativize(lib_dir, output_dir)
1513 for lib_dir in _get_dir_names(dylibs)
1514 ])
1515
1516def _get_dir_names(files):
1517 """Returns a list of directory names from the given list of File objects
1518
1519 Args:
1520 files (list): A list of File objects
1521
1522 Returns:
1523 list: A list of directory names for all files
1524 """
1525 dirs = {}
1526 for f in files:
1527 dirs[f.dirname] = None
1528 return dirs.keys()
1529
Brian Silverman5f6f2762022-08-13 19:30:05 -07001530def add_crate_link_flags(args, dep_info, force_all_deps_direct = False, use_metadata = False):
Brian Silvermancc09f182022-03-09 15:40:20 -08001531 """Adds link flags to an Args object reference
1532
1533 Args:
1534 args (Args): An arguments object reference
1535 dep_info (DepInfo): The current target's dependency info
1536 force_all_deps_direct (bool, optional): Whether to pass the transitive rlibs with --extern
1537 to the commandline as opposed to -L.
Brian Silverman5f6f2762022-08-13 19:30:05 -07001538 use_metadata (bool, optional): Build command line arugments using metadata for crates that provide it.
Brian Silvermancc09f182022-03-09 15:40:20 -08001539 """
1540
Brian Silverman5f6f2762022-08-13 19:30:05 -07001541 direct_crates = depset(
1542 transitive = [
1543 dep_info.direct_crates,
1544 dep_info.transitive_crates,
1545 ],
1546 ) if force_all_deps_direct else dep_info.direct_crates
1547
1548 crate_to_link_flags = _crate_to_link_flag_metadata if use_metadata else _crate_to_link_flag
1549 args.add_all(direct_crates, uniquify = True, map_each = crate_to_link_flags)
1550
Brian Silvermancc09f182022-03-09 15:40:20 -08001551 args.add_all(
1552 dep_info.transitive_crates,
1553 map_each = _get_crate_dirname,
1554 uniquify = True,
1555 format_each = "-Ldependency=%s",
1556 )
1557
Brian Silverman5f6f2762022-08-13 19:30:05 -07001558def _crate_to_link_flag_metadata(crate):
1559 """A helper macro used by `add_crate_link_flags` for adding crate link flags to a Arg object
1560
1561 Args:
1562 crate (CrateInfo|AliasableDepInfo): A CrateInfo or an AliasableDepInfo provider
1563
1564 Returns:
1565 list: Link flags for the given provider
1566 """
1567
1568 # This is AliasableDepInfo, we should use the alias as a crate name
1569 if hasattr(crate, "dep"):
1570 name = crate.name
1571 crate_info = crate.dep
1572 else:
1573 name = crate.name
1574 crate_info = crate
1575
1576 lib_or_meta = crate_info.metadata
1577 if not crate_info.metadata:
1578 lib_or_meta = crate_info.output
1579 return ["--extern={}={}".format(name, lib_or_meta.path)]
1580
Brian Silvermancc09f182022-03-09 15:40:20 -08001581def _crate_to_link_flag(crate):
1582 """A helper macro used by `add_crate_link_flags` for adding crate link flags to a Arg object
1583
1584 Args:
1585 crate (CrateInfo|AliasableDepInfo): A CrateInfo or an AliasableDepInfo provider
1586
1587 Returns:
1588 list: Link flags for the given provider
1589 """
1590
1591 # This is AliasableDepInfo, we should use the alias as a crate name
1592 if hasattr(crate, "dep"):
1593 name = crate.name
1594 crate_info = crate.dep
1595 else:
1596 name = crate.name
1597 crate_info = crate
1598 return ["--extern={}={}".format(name, crate_info.output.path)]
1599
1600def _get_crate_dirname(crate):
1601 """A helper macro used by `add_crate_link_flags` for getting the directory name of the current crate's output path
1602
1603 Args:
1604 crate (CrateInfo): A CrateInfo provider from the current rule
1605
1606 Returns:
1607 str: The directory name of the the output File that will be produced.
1608 """
1609 return crate.output.dirname
1610
Brian Silverman5f6f2762022-08-13 19:30:05 -07001611def _portable_link_flags(lib, use_pic, ambiguous_libs, get_lib_name, for_windows):
Brian Silvermancc09f182022-03-09 15:40:20 -08001612 artifact = get_preferred_artifact(lib, use_pic)
1613 if ambiguous_libs and artifact.path in ambiguous_libs:
1614 artifact = ambiguous_libs[artifact.path]
1615 if lib.static_library or lib.pic_static_library:
Brian Silverman5f6f2762022-08-13 19:30:05 -07001616 # To ensure appropriate linker library argument order, in the presence
1617 # of both native libraries that depend on rlibs and rlibs that depend
1618 # on native libraries, we use an approach where we "sandwich" the
1619 # rust libraries between two similar sections of all of native
1620 # libraries:
1621 # n1 n2 ... r1 r2 ... n1 n2 ...
1622 # A B C
1623 # This way any dependency from a native library to a rust library
1624 # is resolved from A to B, and any dependency from a rust library to
1625 # a native one is resolved from B to C.
1626 # The question of resolving dependencies from a native library from A
1627 # to any rust library is addressed in a different place, where we
1628 # create symlinks to the rlibs, pretending they are native libraries,
1629 # and adding references to these symlinks in the native section A.
1630 # We rely in the behavior of -Clink-arg to put the linker args
1631 # at the end of the linker invocation constructed by rustc.
1632
1633 # We skip adding `-Clink-arg=-l` for libstd and libtest from the standard library, as
1634 # these two libraries are present both as an `.rlib` and a `.so` format.
1635 # On linux, Rustc adds a -Bdynamic to the linker command line before the libraries specified
1636 # with `-Clink-arg`, which leads to us linking against the `.so`s but not putting the
1637 # corresponding value to the runtime library search paths, which results in a
1638 # "cannot open shared object file: No such file or directory" error at exectuion time.
1639 # We can fix this by adding a `-Clink-arg=-Bstatic` on linux, but we don't have that option for
1640 # macos. The proper solution for this issue would be to remove `libtest-{hash}.so` and `libstd-{hash}.so`
1641 # from the toolchain. However, it is not enough to change the toolchain's `rust_std_{...}` filegroups
1642 # here: https://github.com/bazelbuild/rules_rust/blob/a9d5d894ad801002d007b858efd154e503796b9f/rust/private/repository_utils.bzl#L144
1643 # because rustc manages to escape the sandbox and still finds them at linking time.
1644 # We need to modify the repository rules to erase those files completely.
1645 if "lib/rustlib" in artifact.path and (
1646 artifact.basename.startswith("libtest-") or artifact.basename.startswith("libstd-") or
1647 artifact.basename.startswith("test-") or artifact.basename.startswith("std-")
1648 ):
1649 return ["-lstatic=%s" % get_lib_name(artifact)]
Brian Silvermancc09f182022-03-09 15:40:20 -08001650 return [
1651 "-lstatic=%s" % get_lib_name(artifact),
Brian Silverman5f6f2762022-08-13 19:30:05 -07001652 "-Clink-arg=-l%s" % (get_lib_name(artifact) if not for_windows else artifact.basename),
Brian Silvermancc09f182022-03-09 15:40:20 -08001653 ]
1654 elif _is_dylib(lib):
1655 return [
1656 "-ldylib=%s" % get_lib_name(artifact),
1657 ]
1658
1659 return []
1660
1661def _make_link_flags_windows(linker_input_and_use_pic_and_ambiguous_libs):
1662 linker_input, use_pic, ambiguous_libs = linker_input_and_use_pic_and_ambiguous_libs
1663 ret = []
1664 for lib in linker_input.libraries:
1665 if lib.alwayslink:
1666 ret.extend(["-C", "link-arg=/WHOLEARCHIVE:%s" % get_preferred_artifact(lib, use_pic).path])
1667 else:
Brian Silverman5f6f2762022-08-13 19:30:05 -07001668 ret.extend(_portable_link_flags(lib, use_pic, ambiguous_libs, get_lib_name_for_windows, for_windows = True))
Brian Silvermancc09f182022-03-09 15:40:20 -08001669 return ret
1670
1671def _make_link_flags_darwin(linker_input_and_use_pic_and_ambiguous_libs):
1672 linker_input, use_pic, ambiguous_libs = linker_input_and_use_pic_and_ambiguous_libs
1673 ret = []
1674 for lib in linker_input.libraries:
1675 if lib.alwayslink:
1676 ret.extend([
1677 "-C",
1678 ("link-arg=-Wl,-force_load,%s" % get_preferred_artifact(lib, use_pic).path),
1679 ])
1680 else:
Brian Silverman5f6f2762022-08-13 19:30:05 -07001681 ret.extend(_portable_link_flags(lib, use_pic, ambiguous_libs, get_lib_name_default, for_windows = False))
Brian Silvermancc09f182022-03-09 15:40:20 -08001682 return ret
1683
1684def _make_link_flags_default(linker_input_and_use_pic_and_ambiguous_libs):
1685 linker_input, use_pic, ambiguous_libs = linker_input_and_use_pic_and_ambiguous_libs
1686 ret = []
1687 for lib in linker_input.libraries:
1688 if lib.alwayslink:
1689 ret.extend([
1690 "-C",
1691 "link-arg=-Wl,--whole-archive",
1692 "-C",
1693 ("link-arg=%s" % get_preferred_artifact(lib, use_pic).path),
1694 "-C",
1695 "link-arg=-Wl,--no-whole-archive",
1696 ])
1697 else:
Brian Silverman5f6f2762022-08-13 19:30:05 -07001698 ret.extend(_portable_link_flags(lib, use_pic, ambiguous_libs, get_lib_name_default, for_windows = False))
Brian Silvermancc09f182022-03-09 15:40:20 -08001699 return ret
1700
1701def _libraries_dirnames(linker_input_and_use_pic_and_ambiguous_libs):
1702 link_input, use_pic, _ = linker_input_and_use_pic_and_ambiguous_libs
1703
1704 # De-duplicate names.
1705 return depset([get_preferred_artifact(lib, use_pic).dirname for lib in link_input.libraries]).to_list()
1706
1707def _add_native_link_flags(args, dep_info, linkstamp_outs, ambiguous_libs, crate_type, toolchain, cc_toolchain, feature_configuration):
1708 """Adds linker flags for all dependencies of the current target.
1709
1710 Args:
1711 args (Args): The Args struct for a ctx.action
1712 dep_info (DepInfo): Dependency Info provider
1713 linkstamp_outs (list): Linkstamp outputs of native dependencies
1714 ambiguous_libs (dict): Ambiguous libs, see `_disambiguate_libs`
1715 crate_type: Crate type of the current target
1716 toolchain (rust_toolchain): The current `rust_toolchain`
1717 cc_toolchain (CcToolchainInfo): The current `cc_toolchain`
1718 feature_configuration (FeatureConfiguration): feature configuration to use with cc_toolchain
Brian Silvermancc09f182022-03-09 15:40:20 -08001719 """
1720 if crate_type in ["lib", "rlib"]:
1721 return
1722
1723 use_pic = _should_use_pic(cc_toolchain, feature_configuration, crate_type)
1724
1725 if toolchain.os == "windows":
1726 make_link_flags = _make_link_flags_windows
Brian Silverman5f6f2762022-08-13 19:30:05 -07001727 get_lib_name = get_lib_name_for_windows
Brian Silvermancc09f182022-03-09 15:40:20 -08001728 elif toolchain.os.startswith("mac") or toolchain.os.startswith("darwin"):
1729 make_link_flags = _make_link_flags_darwin
Brian Silverman5f6f2762022-08-13 19:30:05 -07001730 get_lib_name = get_lib_name_default
Brian Silvermancc09f182022-03-09 15:40:20 -08001731 else:
1732 make_link_flags = _make_link_flags_default
Brian Silverman5f6f2762022-08-13 19:30:05 -07001733 get_lib_name = get_lib_name_default
Brian Silvermancc09f182022-03-09 15:40:20 -08001734
1735 # TODO(hlopko): Remove depset flattening by using lambdas once we are on >=Bazel 5.0
1736 args_and_pic_and_ambiguous_libs = [(arg, use_pic, ambiguous_libs) for arg in dep_info.transitive_noncrates.to_list()]
1737 args.add_all(args_and_pic_and_ambiguous_libs, map_each = _libraries_dirnames, uniquify = True, format_each = "-Lnative=%s")
1738 if ambiguous_libs:
1739 # If there are ambiguous libs, the disambiguation symlinks to them are
1740 # all created in the same directory. Add it to the library search path.
1741 ambiguous_libs_dirname = ambiguous_libs.values()[0].dirname
1742 args.add("-Lnative={}".format(ambiguous_libs_dirname))
1743
1744 args.add_all(args_and_pic_and_ambiguous_libs, map_each = make_link_flags)
1745
1746 for linkstamp_out in linkstamp_outs:
1747 args.add_all(["-C", "link-arg=%s" % linkstamp_out.path])
1748
1749 if crate_type in ["dylib", "cdylib"]:
1750 # For shared libraries we want to link C++ runtime library dynamically
1751 # (for example libstdc++.so or libc++.so).
1752 args.add_all(
1753 cc_toolchain.dynamic_runtime_lib(feature_configuration = feature_configuration),
1754 map_each = _get_dirname,
1755 format_each = "-Lnative=%s",
1756 )
1757 args.add_all(
1758 cc_toolchain.dynamic_runtime_lib(feature_configuration = feature_configuration),
1759 map_each = get_lib_name,
1760 format_each = "-ldylib=%s",
1761 )
1762 else:
1763 # For all other crate types we want to link C++ runtime library statically
1764 # (for example libstdc++.a or libc++.a).
1765 args.add_all(
1766 cc_toolchain.static_runtime_lib(feature_configuration = feature_configuration),
1767 map_each = _get_dirname,
1768 format_each = "-Lnative=%s",
1769 )
1770 args.add_all(
1771 cc_toolchain.static_runtime_lib(feature_configuration = feature_configuration),
1772 map_each = get_lib_name,
1773 format_each = "-lstatic=%s",
1774 )
1775
1776def _get_dirname(file):
1777 """A helper function for `_add_native_link_flags`.
1778
1779 Args:
1780 file (File): The target file
1781
1782 Returns:
1783 str: Directory name of `file`
1784 """
1785 return file.dirname
1786
1787def _error_format_impl(ctx):
1788 """Implementation of the `error_format` rule
1789
1790 Args:
1791 ctx (ctx): The rule's context object
1792
1793 Returns:
1794 list: A list containing the ErrorFormatInfo provider
1795 """
1796 raw = ctx.build_setting_value
1797 if raw not in _error_format_values:
1798 fail("{} expected a value in `{}` but got `{}`".format(
1799 ctx.label,
1800 _error_format_values,
1801 raw,
1802 ))
1803 return [ErrorFormatInfo(error_format = raw)]
1804
1805error_format = rule(
1806 doc = (
1807 "Change the [--error-format](https://doc.rust-lang.org/rustc/command-line-arguments.html#option-error-format) " +
1808 "flag from the command line with `--@rules_rust//:error_format`. See rustc documentation for valid values."
1809 ),
1810 implementation = _error_format_impl,
1811 build_setting = config.string(flag = True),
1812)
1813
1814def _extra_rustc_flags_impl(ctx):
1815 return ExtraRustcFlagsInfo(extra_rustc_flags = ctx.build_setting_value)
1816
1817extra_rustc_flags = rule(
1818 doc = (
1819 "Add additional rustc_flags from the command line with `--@rules_rust//:extra_rustc_flags`. " +
1820 "This flag should only be used for flags that need to be applied across the entire build. For options that " +
1821 "apply to individual crates, use the rustc_flags attribute on the individual crate's rule instead. NOTE: " +
1822 "These flags not applied to the exec configuration (proc-macros, cargo_build_script, etc); " +
1823 "use `--@rules_rust//:extra_exec_rustc_flags` to apply flags to the exec configuration."
1824 ),
1825 implementation = _extra_rustc_flags_impl,
1826 build_setting = config.string_list(flag = True),
1827)
1828
Brian Silverman5f6f2762022-08-13 19:30:05 -07001829def _extra_rustc_flag_impl(ctx):
1830 return ExtraRustcFlagsInfo(extra_rustc_flags = [f for f in ctx.build_setting_value if f != ""])
1831
1832extra_rustc_flag = rule(
1833 doc = (
1834 "Add additional rustc_flag from the command line with `--@rules_rust//:extra_rustc_flag`. " +
1835 "Multiple uses are accumulated and appended after the extra_rustc_flags."
1836 ),
1837 implementation = _extra_rustc_flag_impl,
1838 build_setting = config.string(flag = True, allow_multiple = True),
1839)
1840
Brian Silvermancc09f182022-03-09 15:40:20 -08001841def _extra_exec_rustc_flags_impl(ctx):
1842 return ExtraExecRustcFlagsInfo(extra_exec_rustc_flags = ctx.build_setting_value)
1843
1844extra_exec_rustc_flags = rule(
1845 doc = (
1846 "Add additional rustc_flags in the exec configuration from the command line with `--@rules_rust//:extra_exec_rustc_flags`. " +
1847 "This flag should only be used for flags that need to be applied across the entire build. " +
1848 "These flags only apply to the exec configuration (proc-macros, cargo_build_script, etc)."
1849 ),
1850 implementation = _extra_exec_rustc_flags_impl,
1851 build_setting = config.string_list(flag = True),
1852)
Brian Silverman5f6f2762022-08-13 19:30:05 -07001853
1854def _extra_exec_rustc_flag_impl(ctx):
1855 return ExtraExecRustcFlagsInfo(extra_exec_rustc_flags = [f for f in ctx.build_setting_value if f != ""])
1856
1857extra_exec_rustc_flag = rule(
1858 doc = (
1859 "Add additional rustc_flags in the exec configuration from the command line with `--@rules_rust//:extra_exec_rustc_flag`. " +
1860 "Multiple uses are accumulated and appended after the extra_exec_rustc_flags."
1861 ),
1862 implementation = _extra_exec_rustc_flag_impl,
1863 build_setting = config.string(flag = True, allow_multiple = True),
1864)