1use std::collections::{HashMap, HashSet};
39use std::hash::{Hash, Hasher};
40use std::sync::Arc;
41
42use crate::core::compiler::unit_dependencies::build_unit_dependencies;
43use crate::core::compiler::unit_graph::{self, UnitDep, UnitGraph};
44use crate::core::compiler::{apply_env_config, standard_lib, CrateType, TargetInfo};
45use crate::core::compiler::{BuildConfig, BuildContext, BuildRunner, Compilation};
46use crate::core::compiler::{CompileKind, CompileMode, CompileTarget, RustcTargetData, Unit};
47use crate::core::compiler::{DefaultExecutor, Executor, UnitInterner};
48use crate::core::profiles::Profiles;
49use crate::core::resolver::features::{self, CliFeatures, FeaturesFor};
50use crate::core::resolver::{HasDevUnits, Resolve};
51use crate::core::{PackageId, PackageSet, SourceId, TargetKind, Workspace};
52use crate::drop_println;
53use crate::ops;
54use crate::ops::resolve::WorkspaceResolve;
55use crate::util::context::{GlobalContext, WarningHandling};
56use crate::util::interning::InternedString;
57use crate::util::{CargoResult, StableHasher};
58
59mod compile_filter;
60pub use compile_filter::{CompileFilter, FilterRule, LibRule};
61
62mod unit_generator;
63use unit_generator::UnitGenerator;
64
65mod packages;
66
67pub use packages::Packages;
68
69#[derive(Debug, Clone)]
78pub struct CompileOptions {
79 pub build_config: BuildConfig,
81 pub cli_features: CliFeatures,
83 pub spec: Packages,
85 pub filter: CompileFilter,
88 pub target_rustdoc_args: Option<Vec<String>>,
90 pub target_rustc_args: Option<Vec<String>>,
93 pub target_rustc_crate_types: Option<Vec<String>>,
95 pub rustdoc_document_private_items: bool,
98 pub honor_rust_version: Option<bool>,
101}
102
103impl CompileOptions {
104 pub fn new(gctx: &GlobalContext, mode: CompileMode) -> CargoResult<CompileOptions> {
105 let jobs = None;
106 let keep_going = false;
107 Ok(CompileOptions {
108 build_config: BuildConfig::new(gctx, jobs, keep_going, &[], mode)?,
109 cli_features: CliFeatures::new_all(false),
110 spec: ops::Packages::Packages(Vec::new()),
111 filter: CompileFilter::Default {
112 required_features_filterable: false,
113 },
114 target_rustdoc_args: None,
115 target_rustc_args: None,
116 target_rustc_crate_types: None,
117 rustdoc_document_private_items: false,
118 honor_rust_version: None,
119 })
120 }
121}
122
123pub fn compile<'a>(ws: &Workspace<'a>, options: &CompileOptions) -> CargoResult<Compilation<'a>> {
127 let exec: Arc<dyn Executor> = Arc::new(DefaultExecutor);
128 compile_with_exec(ws, options, &exec)
129}
130
131pub fn compile_with_exec<'a>(
136 ws: &Workspace<'a>,
137 options: &CompileOptions,
138 exec: &Arc<dyn Executor>,
139) -> CargoResult<Compilation<'a>> {
140 ws.emit_warnings()?;
141 let compilation = compile_ws(ws, options, exec)?;
142 if ws.gctx().warning_handling()? == WarningHandling::Deny && compilation.warning_count > 0 {
143 anyhow::bail!("warnings are denied by `build.warnings` configuration")
144 }
145 Ok(compilation)
146}
147
148#[tracing::instrument(skip_all)]
150pub fn compile_ws<'a>(
151 ws: &Workspace<'a>,
152 options: &CompileOptions,
153 exec: &Arc<dyn Executor>,
154) -> CargoResult<Compilation<'a>> {
155 let interner = UnitInterner::new();
156 let bcx = create_bcx(ws, options, &interner)?;
157 if options.build_config.unit_graph {
158 unit_graph::emit_serialized_unit_graph(&bcx.roots, &bcx.unit_graph, ws.gctx())?;
159 return Compilation::new(&bcx);
160 }
161 crate::core::gc::auto_gc(bcx.gctx);
162 let build_runner = BuildRunner::new(&bcx)?;
163 if options.build_config.dry_run {
164 build_runner.dry_run()
165 } else {
166 build_runner.compile(exec)
167 }
168}
169
170pub fn print<'a>(
174 ws: &Workspace<'a>,
175 options: &CompileOptions,
176 print_opt_value: &str,
177) -> CargoResult<()> {
178 let CompileOptions {
179 ref build_config,
180 ref target_rustc_args,
181 ..
182 } = *options;
183 let gctx = ws.gctx();
184 let rustc = gctx.load_global_rustc(Some(ws))?;
185 for (index, kind) in build_config.requested_kinds.iter().enumerate() {
186 if index != 0 {
187 drop_println!(gctx);
188 }
189 let target_info = TargetInfo::new(gctx, &build_config.requested_kinds, &rustc, *kind)?;
190 let mut process = rustc.process();
191 apply_env_config(gctx, &mut process)?;
192 process.args(&target_info.rustflags);
193 if let Some(args) = target_rustc_args {
194 process.args(args);
195 }
196 if let CompileKind::Target(t) = kind {
197 process.arg("--target").arg(t.rustc_target());
198 }
199 process.arg("--print").arg(print_opt_value);
200 process.exec()?;
201 }
202 Ok(())
203}
204
205#[tracing::instrument(skip_all)]
210pub fn create_bcx<'a, 'gctx>(
211 ws: &'a Workspace<'gctx>,
212 options: &'a CompileOptions,
213 interner: &'a UnitInterner,
214) -> CargoResult<BuildContext<'a, 'gctx>> {
215 let CompileOptions {
216 ref build_config,
217 ref spec,
218 ref cli_features,
219 ref filter,
220 ref target_rustdoc_args,
221 ref target_rustc_args,
222 ref target_rustc_crate_types,
223 rustdoc_document_private_items,
224 honor_rust_version,
225 } = *options;
226 let gctx = ws.gctx();
227
228 match build_config.mode {
230 CompileMode::Test
231 | CompileMode::Build
232 | CompileMode::Check { .. }
233 | CompileMode::Bench
234 | CompileMode::RunCustomBuild => {
235 if ws.gctx().get_env("RUST_FLAGS").is_ok() {
236 gctx.shell().warn(
237 "Cargo does not read `RUST_FLAGS` environment variable. Did you mean `RUSTFLAGS`?",
238 )?;
239 }
240 }
241 CompileMode::Doc { .. } | CompileMode::Doctest | CompileMode::Docscrape => {
242 if ws.gctx().get_env("RUSTDOC_FLAGS").is_ok() {
243 gctx.shell().warn(
244 "Cargo does not read `RUSTDOC_FLAGS` environment variable. Did you mean `RUSTDOCFLAGS`?"
245 )?;
246 }
247 }
248 }
249 gctx.validate_term_config()?;
250
251 let mut target_data = RustcTargetData::new(ws, &build_config.requested_kinds)?;
252
253 let specs = spec.to_package_id_specs(ws)?;
254 let has_dev_units = {
255 let any_pkg_has_scrape_enabled = ws
259 .members_with_features(&specs, cli_features)?
260 .iter()
261 .any(|(pkg, _)| {
262 pkg.targets()
263 .iter()
264 .any(|target| target.is_example() && target.doc_scrape_examples().is_enabled())
265 });
266
267 if filter.need_dev_deps(build_config.mode)
268 || (build_config.mode.is_doc() && any_pkg_has_scrape_enabled)
269 {
270 HasDevUnits::Yes
271 } else {
272 HasDevUnits::No
273 }
274 };
275 let dry_run = false;
276 let resolve = ops::resolve_ws_with_opts(
277 ws,
278 &mut target_data,
279 &build_config.requested_kinds,
280 cli_features,
281 &specs,
282 has_dev_units,
283 crate::core::resolver::features::ForceAllTargets::No,
284 dry_run,
285 )?;
286 let WorkspaceResolve {
287 mut pkg_set,
288 workspace_resolve,
289 targeted_resolve: resolve,
290 resolved_features,
291 } = resolve;
292
293 let std_resolve_features = if let Some(crates) = &gctx.cli_unstable().build_std {
294 let (std_package_set, std_resolve, std_features) = standard_lib::resolve_std(
295 ws,
296 &mut target_data,
297 &build_config,
298 crates,
299 &build_config.requested_kinds,
300 )?;
301 pkg_set.add_set(std_package_set);
302 Some((std_resolve, std_features))
303 } else {
304 None
305 };
306
307 let to_build_ids = resolve.specs_to_ids(&specs)?;
311 let mut to_builds = pkg_set.get_many(to_build_ids)?;
315
316 to_builds.sort_by_key(|p| p.package_id());
320
321 for pkg in to_builds.iter() {
322 pkg.manifest().print_teapot(gctx);
323
324 if build_config.mode.is_any_test()
325 && !ws.is_member(pkg)
326 && pkg.dependencies().iter().any(|dep| !dep.is_transitive())
327 {
328 anyhow::bail!(
329 "package `{}` cannot be tested because it requires dev-dependencies \
330 and is not a member of the workspace",
331 pkg.name()
332 );
333 }
334 }
335
336 let (extra_args, extra_args_name) = match (target_rustc_args, target_rustdoc_args) {
337 (Some(args), _) => (Some(args.clone()), "rustc"),
338 (_, Some(args)) => (Some(args.clone()), "rustdoc"),
339 _ => (None, ""),
340 };
341
342 if extra_args.is_some() && to_builds.len() != 1 {
343 panic!(
344 "`{}` should not accept multiple `-p` flags",
345 extra_args_name
346 );
347 }
348
349 let profiles = Profiles::new(ws, build_config.requested_profile)?;
350 profiles.validate_packages(
351 ws.profiles(),
352 &mut gctx.shell(),
353 workspace_resolve.as_ref().unwrap_or(&resolve),
354 )?;
355
356 let explicit_host_kind = CompileKind::Target(CompileTarget::new(&target_data.rustc.host)?);
360 let explicit_host_kinds: Vec<_> = build_config
361 .requested_kinds
362 .iter()
363 .map(|kind| match kind {
364 CompileKind::Host => explicit_host_kind,
365 CompileKind::Target(t) => CompileKind::Target(*t),
366 })
367 .collect();
368
369 let generator = UnitGenerator {
375 ws,
376 packages: &to_builds,
377 spec,
378 target_data: &target_data,
379 filter,
380 requested_kinds: &build_config.requested_kinds,
381 explicit_host_kind,
382 mode: build_config.mode,
383 resolve: &resolve,
384 workspace_resolve: &workspace_resolve,
385 resolved_features: &resolved_features,
386 package_set: &pkg_set,
387 profiles: &profiles,
388 interner,
389 has_dev_units,
390 };
391 let mut units = generator.generate_root_units()?;
392
393 if let Some(args) = target_rustc_crate_types {
394 override_rustc_crate_types(&mut units, args, interner)?;
395 }
396
397 let should_scrape = build_config.mode.is_doc() && gctx.cli_unstable().rustdoc_scrape_examples;
398 let mut scrape_units = if should_scrape {
399 UnitGenerator {
400 mode: CompileMode::Docscrape,
401 ..generator
402 }
403 .generate_scrape_units(&units)?
404 } else {
405 Vec::new()
406 };
407
408 let std_roots = if let Some(crates) = gctx.cli_unstable().build_std.as_ref() {
409 let (std_resolve, std_features) = std_resolve_features.as_ref().unwrap();
410 standard_lib::generate_std_roots(
411 &crates,
412 &units,
413 std_resolve,
414 std_features,
415 &explicit_host_kinds,
416 &pkg_set,
417 interner,
418 &profiles,
419 &target_data,
420 )?
421 } else {
422 Default::default()
423 };
424
425 let mut unit_graph = build_unit_dependencies(
426 ws,
427 &pkg_set,
428 &resolve,
429 &resolved_features,
430 std_resolve_features.as_ref(),
431 &units,
432 &scrape_units,
433 &std_roots,
434 build_config.mode,
435 &target_data,
436 &profiles,
437 interner,
438 )?;
439
440 if matches!(build_config.mode, CompileMode::Doc { deps: true, .. }) {
443 remove_duplicate_doc(build_config, &units, &mut unit_graph);
444 }
445
446 let host_kind_requested = build_config
447 .requested_kinds
448 .iter()
449 .any(CompileKind::is_host);
450 (units, scrape_units, unit_graph) = rebuild_unit_graph_shared(
454 interner,
455 unit_graph,
456 &units,
457 &scrape_units,
458 host_kind_requested.then_some(explicit_host_kind),
459 );
460
461 let mut extra_compiler_args = HashMap::new();
462 if let Some(args) = extra_args {
463 if units.len() != 1 {
464 anyhow::bail!(
465 "extra arguments to `{}` can only be passed to one \
466 target, consider filtering\nthe package by passing, \
467 e.g., `--lib` or `--bin NAME` to specify a single target",
468 extra_args_name
469 );
470 }
471 extra_compiler_args.insert(units[0].clone(), args);
472 }
473
474 for unit in units
475 .iter()
476 .filter(|unit| unit.mode.is_doc() || unit.mode.is_doc_test())
477 .filter(|unit| rustdoc_document_private_items || unit.target.is_bin())
478 {
479 let mut args = vec!["--document-private-items".into()];
483 if unit.target.is_bin() {
484 args.push("-Arustdoc::private-intra-doc-links".into());
488 }
489 extra_compiler_args
490 .entry(unit.clone())
491 .or_default()
492 .extend(args);
493 }
494
495 if honor_rust_version.unwrap_or(true) {
496 let rustc_version = target_data.rustc.version.clone().into();
497
498 let mut incompatible = Vec::new();
499 let mut local_incompatible = false;
500 for unit in unit_graph.keys() {
501 let Some(pkg_msrv) = unit.pkg.rust_version() else {
502 continue;
503 };
504
505 if pkg_msrv.is_compatible_with(&rustc_version) {
506 continue;
507 }
508
509 local_incompatible |= unit.is_local();
510 incompatible.push((unit, pkg_msrv));
511 }
512 if !incompatible.is_empty() {
513 use std::fmt::Write as _;
514
515 let plural = if incompatible.len() == 1 { "" } else { "s" };
516 let mut message = format!(
517 "rustc {rustc_version} is not supported by the following package{plural}:\n"
518 );
519 incompatible.sort_by_key(|(unit, _)| (unit.pkg.name(), unit.pkg.version()));
520 for (unit, msrv) in incompatible {
521 let name = &unit.pkg.name();
522 let version = &unit.pkg.version();
523 writeln!(&mut message, " {name}@{version} requires rustc {msrv}").unwrap();
524 }
525 if ws.is_ephemeral() {
526 if ws.ignore_lock() {
527 writeln!(
528 &mut message,
529 "Try re-running `cargo install` with `--locked`"
530 )
531 .unwrap();
532 }
533 } else if !local_incompatible {
534 writeln!(
535 &mut message,
536 "Either upgrade rustc or select compatible dependency versions with
537`cargo update <name>@<current-ver> --precise <compatible-ver>`
538where `<compatible-ver>` is the latest version supporting rustc {rustc_version}",
539 )
540 .unwrap();
541 }
542 return Err(anyhow::Error::msg(message));
543 }
544 }
545
546 let bcx = BuildContext::new(
547 ws,
548 pkg_set,
549 build_config,
550 profiles,
551 extra_compiler_args,
552 target_data,
553 units,
554 unit_graph,
555 scrape_units,
556 )?;
557
558 Ok(bcx)
559}
560
561fn rebuild_unit_graph_shared(
593 interner: &UnitInterner,
594 unit_graph: UnitGraph,
595 roots: &[Unit],
596 scrape_units: &[Unit],
597 to_host: Option<CompileKind>,
598) -> (Vec<Unit>, Vec<Unit>, UnitGraph) {
599 let mut result = UnitGraph::new();
600 let mut memo = HashMap::new();
603 let new_roots = roots
604 .iter()
605 .map(|root| {
606 traverse_and_share(
607 interner,
608 &mut memo,
609 &mut result,
610 &unit_graph,
611 root,
612 false,
613 to_host,
614 )
615 })
616 .collect();
617 let new_scrape_units = scrape_units
621 .iter()
622 .map(|unit| memo.get(unit).unwrap().clone())
623 .collect();
624 (new_roots, new_scrape_units, result)
625}
626
627fn traverse_and_share(
633 interner: &UnitInterner,
634 memo: &mut HashMap<Unit, Unit>,
635 new_graph: &mut UnitGraph,
636 unit_graph: &UnitGraph,
637 unit: &Unit,
638 unit_is_for_host: bool,
639 to_host: Option<CompileKind>,
640) -> Unit {
641 if let Some(new_unit) = memo.get(unit) {
642 return new_unit.clone();
644 }
645 let mut dep_hash = StableHasher::new();
646 let new_deps: Vec<_> = unit_graph[unit]
647 .iter()
648 .map(|dep| {
649 let new_dep_unit = traverse_and_share(
650 interner,
651 memo,
652 new_graph,
653 unit_graph,
654 &dep.unit,
655 dep.unit_for.is_for_host(),
656 to_host,
657 );
658 new_dep_unit.hash(&mut dep_hash);
659 UnitDep {
660 unit: new_dep_unit,
661 ..dep.clone()
662 }
663 })
664 .collect();
665 let new_dep_hash = Hasher::finish(&dep_hash);
668
669 let canonical_kind = match to_host {
676 Some(to_host) if to_host == unit.kind => CompileKind::Host,
677 _ => unit.kind,
678 };
679
680 let mut profile = unit.profile.clone();
681 if profile.strip.is_deferred() {
682 if !profile.debuginfo.is_turned_on()
686 && new_deps
687 .iter()
688 .all(|dep| !dep.unit.profile.debuginfo.is_turned_on())
689 {
690 profile.strip = profile.strip.strip_debuginfo();
691 }
692 }
693
694 if unit_is_for_host
698 && to_host.is_some()
699 && profile.debuginfo.is_deferred()
700 && !unit.artifact.is_true()
701 {
702 let canonical_debuginfo = profile.debuginfo.finalize();
706 let mut canonical_profile = profile.clone();
707 canonical_profile.debuginfo = canonical_debuginfo;
708 let unit_probe = interner.intern(
709 &unit.pkg,
710 &unit.target,
711 canonical_profile,
712 to_host.unwrap(),
713 unit.mode,
714 unit.features.clone(),
715 unit.rustflags.clone(),
716 unit.rustdocflags.clone(),
717 unit.links_overrides.clone(),
718 unit.is_std,
719 unit.dep_hash,
720 unit.artifact,
721 unit.artifact_target_for_features,
722 );
723
724 profile.debuginfo = if unit_graph.contains_key(&unit_probe) {
726 canonical_debuginfo
729 } else {
730 canonical_debuginfo.weaken()
733 }
734 }
735
736 let new_unit = interner.intern(
737 &unit.pkg,
738 &unit.target,
739 profile,
740 canonical_kind,
741 unit.mode,
742 unit.features.clone(),
743 unit.rustflags.clone(),
744 unit.rustdocflags.clone(),
745 unit.links_overrides.clone(),
746 unit.is_std,
747 new_dep_hash,
748 unit.artifact,
749 None,
752 );
753 assert!(memo.insert(unit.clone(), new_unit.clone()).is_none());
754 new_graph.entry(new_unit.clone()).or_insert(new_deps);
755 new_unit
756}
757
758fn remove_duplicate_doc(
774 build_config: &BuildConfig,
775 root_units: &[Unit],
776 unit_graph: &mut UnitGraph,
777) {
778 let mut all_docs: HashMap<String, Vec<Unit>> = HashMap::new();
781 for unit in unit_graph.keys() {
782 if unit.mode.is_doc() {
783 all_docs
784 .entry(unit.target.crate_name())
785 .or_default()
786 .push(unit.clone());
787 }
788 }
789 let mut removed_units: HashSet<Unit> = HashSet::new();
792 let mut remove = |units: Vec<Unit>, reason: &str, cb: &dyn Fn(&Unit) -> bool| -> Vec<Unit> {
793 let (to_remove, remaining_units): (Vec<Unit>, Vec<Unit>) = units
794 .into_iter()
795 .partition(|unit| cb(unit) && !root_units.contains(unit));
796 for unit in to_remove {
797 tracing::debug!(
798 "removing duplicate doc due to {} for package {} target `{}`",
799 reason,
800 unit.pkg,
801 unit.target.name()
802 );
803 unit_graph.remove(&unit);
804 removed_units.insert(unit);
805 }
806 remaining_units
807 };
808 for (_crate_name, mut units) in all_docs {
810 if units.len() == 1 {
811 continue;
812 }
813 if build_config
815 .requested_kinds
816 .iter()
817 .all(CompileKind::is_host)
818 {
819 units = remove(units, "host/target merger", &|unit| unit.kind.is_host());
824 if units.len() == 1 {
825 continue;
826 }
827 }
828 let mut source_map: HashMap<(InternedString, SourceId, CompileKind), Vec<Unit>> =
830 HashMap::new();
831 for unit in units {
832 let pkg_id = unit.pkg.package_id();
833 source_map
835 .entry((pkg_id.name(), pkg_id.source_id(), unit.kind))
836 .or_default()
837 .push(unit);
838 }
839 let mut remaining_units = Vec::new();
840 for (_key, mut units) in source_map {
841 if units.len() > 1 {
842 units.sort_by(|a, b| a.pkg.version().partial_cmp(b.pkg.version()).unwrap());
843 let newest_version = units.last().unwrap().pkg.version().clone();
845 let keep_units = remove(units, "older version", &|unit| {
846 unit.pkg.version() < &newest_version
847 });
848 remaining_units.extend(keep_units);
849 } else {
850 remaining_units.extend(units);
851 }
852 }
853 if remaining_units.len() == 1 {
854 continue;
855 }
856 }
859 for unit_deps in unit_graph.values_mut() {
861 unit_deps.retain(|unit_dep| !removed_units.contains(&unit_dep.unit));
862 }
863 let mut visited = HashSet::new();
865 fn visit(unit: &Unit, graph: &UnitGraph, visited: &mut HashSet<Unit>) {
866 if !visited.insert(unit.clone()) {
867 return;
868 }
869 for dep in &graph[unit] {
870 visit(&dep.unit, graph, visited);
871 }
872 }
873 for unit in root_units {
874 visit(unit, unit_graph, &mut visited);
875 }
876 unit_graph.retain(|unit, _| visited.contains(unit));
877}
878
879fn override_rustc_crate_types(
883 units: &mut [Unit],
884 args: &[String],
885 interner: &UnitInterner,
886) -> CargoResult<()> {
887 if units.len() != 1 {
888 anyhow::bail!(
889 "crate types to rustc can only be passed to one \
890 target, consider filtering\nthe package by passing, \
891 e.g., `--lib` or `--example` to specify a single target"
892 );
893 }
894
895 let unit = &units[0];
896 let override_unit = |f: fn(Vec<CrateType>) -> TargetKind| {
897 let crate_types = args.iter().map(|s| s.into()).collect();
898 let mut target = unit.target.clone();
899 target.set_kind(f(crate_types));
900 interner.intern(
901 &unit.pkg,
902 &target,
903 unit.profile.clone(),
904 unit.kind,
905 unit.mode,
906 unit.features.clone(),
907 unit.rustflags.clone(),
908 unit.rustdocflags.clone(),
909 unit.links_overrides.clone(),
910 unit.is_std,
911 unit.dep_hash,
912 unit.artifact,
913 unit.artifact_target_for_features,
914 )
915 };
916 units[0] = match unit.target.kind() {
917 TargetKind::Lib(_) => override_unit(TargetKind::Lib),
918 TargetKind::ExampleLib(_) => override_unit(TargetKind::ExampleLib),
919 _ => {
920 anyhow::bail!(
921 "crate types can only be specified for libraries and example libraries.\n\
922 Binaries, tests, and benchmarks are always the `bin` crate type"
923 );
924 }
925 };
926
927 Ok(())
928}
929
930pub fn resolve_all_features(
936 resolve_with_overrides: &Resolve,
937 resolved_features: &features::ResolvedFeatures,
938 package_set: &PackageSet<'_>,
939 package_id: PackageId,
940) -> HashSet<String> {
941 let mut features: HashSet<String> = resolved_features
942 .activated_features(package_id, FeaturesFor::NormalOrDev)
943 .iter()
944 .map(|s| s.to_string())
945 .collect();
946
947 for (dep_id, deps) in resolve_with_overrides.deps(package_id) {
950 let is_proc_macro = package_set
951 .get_one(dep_id)
952 .expect("packages downloaded")
953 .proc_macro();
954 for dep in deps {
955 let features_for = FeaturesFor::from_for_host(is_proc_macro || dep.is_build());
956 for feature in resolved_features
957 .activated_features_unverified(dep_id, features_for)
958 .unwrap_or_default()
959 {
960 features.insert(format!("{}/{}", dep.name_in_toml(), feature));
961 }
962 }
963 }
964
965 features
966}