guppy/graph/
build.rs

1// Copyright (c) The cargo-guppy Contributors
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4use crate::{
5    Error, PackageId,
6    graph::{
7        BuildTargetImpl, BuildTargetKindImpl, DepRequiredOrOptional, DependencyReqImpl,
8        NamedFeatureDep, OwnedBuildTargetId, PackageGraph, PackageGraphData, PackageIx,
9        PackageLinkImpl, PackageMetadataImpl, PackagePublishImpl, PackageSourceImpl, WorkspaceImpl,
10        cargo_version_matches,
11    },
12    sorted_set::SortedSet,
13};
14use ahash::AHashMap;
15use camino::{Utf8Path, Utf8PathBuf};
16use cargo_metadata::{
17    DepKindInfo, Dependency, DependencyKind, Metadata, Node, NodeDep, Package, Target,
18};
19use fixedbitset::FixedBitSet;
20use indexmap::{IndexMap, IndexSet};
21use once_cell::sync::OnceCell;
22use petgraph::prelude::*;
23use semver::{Version, VersionReq};
24use smallvec::SmallVec;
25use std::{
26    borrow::Cow,
27    cell::RefCell,
28    collections::{BTreeMap, HashSet},
29    rc::Rc,
30};
31use target_spec::TargetSpec;
32
33impl PackageGraph {
34    /// Constructs a new `PackageGraph` instances from the given metadata.
35    pub(crate) fn build(mut metadata: Metadata) -> Result<Self, Box<Error>> {
36        // resolve_nodes is missing if the metadata was generated with --no-deps.
37        let resolve_nodes = metadata.resolve.map(|r| r.nodes).unwrap_or_default();
38
39        let workspace_members: HashSet<_> = metadata
40            .workspace_members
41            .into_iter()
42            .map(PackageId::from_metadata)
43            .collect();
44
45        let workspace_root = metadata.workspace_root;
46
47        let mut build_state = GraphBuildState::new(
48            &mut metadata.packages,
49            resolve_nodes,
50            &workspace_root,
51            &workspace_members,
52        )?;
53
54        let packages: AHashMap<_, _> = metadata
55            .packages
56            .into_iter()
57            .map(|package| build_state.process_package(package))
58            .collect::<Result<_, _>>()?;
59
60        let dep_graph = build_state.finish();
61
62        let workspace = WorkspaceImpl::new(
63            workspace_root,
64            metadata.target_directory,
65            metadata.workspace_metadata,
66            &packages,
67            workspace_members,
68        )?;
69
70        Ok(Self {
71            dep_graph,
72            sccs: OnceCell::new(),
73            feature_graph: OnceCell::new(),
74            data: PackageGraphData {
75                packages,
76                workspace,
77            },
78        })
79    }
80}
81
82impl WorkspaceImpl {
83    /// Indexes and creates a new workspace.
84    fn new(
85        workspace_root: impl Into<Utf8PathBuf>,
86        target_directory: impl Into<Utf8PathBuf>,
87        metadata_table: serde_json::Value,
88        packages: &AHashMap<PackageId, PackageMetadataImpl>,
89        members: impl IntoIterator<Item = PackageId>,
90    ) -> Result<Self, Box<Error>> {
91        use std::collections::btree_map::Entry;
92
93        let workspace_root = workspace_root.into();
94        // Build up the workspace members by path, since most interesting queries are going to
95        // happen by path.
96        let mut members_by_path = BTreeMap::new();
97        let mut members_by_name = BTreeMap::new();
98        for id in members {
99            // Strip off the workspace path from the manifest path.
100            let package_metadata = packages.get(&id).ok_or_else(|| {
101                Error::PackageGraphConstructError(format!("workspace member '{}' not found", id))
102            })?;
103
104            let workspace_path = match &package_metadata.source {
105                PackageSourceImpl::Workspace(path) => path,
106                _ => {
107                    return Err(Error::PackageGraphConstructError(format!(
108                        "workspace member '{}' at path {:?} not in workspace",
109                        id, package_metadata.manifest_path,
110                    ))
111                    .into());
112                }
113            };
114            members_by_path.insert(workspace_path.to_path_buf(), id.clone());
115
116            match members_by_name.entry(package_metadata.name.clone().into_boxed_str()) {
117                Entry::Vacant(vacant) => {
118                    vacant.insert(id.clone());
119                }
120                Entry::Occupied(occupied) => {
121                    return Err(Error::PackageGraphConstructError(format!(
122                        "duplicate package name in workspace: '{}' is name for '{}' and '{}'",
123                        occupied.key(),
124                        occupied.get(),
125                        id
126                    ))
127                    .into());
128                }
129            }
130        }
131
132        Ok(Self {
133            root: workspace_root,
134            target_directory: target_directory.into(),
135            metadata_table,
136            members_by_path,
137            members_by_name,
138            #[cfg(feature = "proptest1")]
139            name_list: OnceCell::new(),
140        })
141    }
142}
143
144/// Helper struct for building up dependency graph.
145struct GraphBuildState<'a> {
146    dep_graph: Graph<PackageId, PackageLinkImpl, Directed, PackageIx>,
147    package_data: AHashMap<PackageId, Rc<PackageDataValue>>,
148    // The above, except by package name.
149    by_package_name: AHashMap<String, Vec<Rc<PackageDataValue>>>,
150
151    // The values of resolve_data are the resolved dependencies. This is mutated so it is stored
152    // separately from package_data.
153    resolve_data: AHashMap<PackageId, Vec<NodeDep>>,
154    workspace_root: &'a Utf8Path,
155    workspace_members: &'a HashSet<PackageId>,
156}
157
158impl<'a> GraphBuildState<'a> {
159    /// This method drains the list of targets from the package.
160    fn new(
161        packages: &mut [Package],
162        resolve_nodes: Vec<Node>,
163        workspace_root: &'a Utf8Path,
164        workspace_members: &'a HashSet<PackageId>,
165    ) -> Result<Self, Box<Error>> {
166        // Precomputing the edge count is a roughly 5% performance improvement.
167        let edge_count = resolve_nodes
168            .iter()
169            .map(|node| node.deps.len())
170            .sum::<usize>();
171
172        let mut dep_graph = Graph::with_capacity(packages.len(), edge_count);
173        let all_package_data: AHashMap<_, _> = packages
174            .iter_mut()
175            .map(|package| PackageDataValue::new(package, &mut dep_graph))
176            .collect::<Result<_, _>>()?;
177
178        // While it is possible to have duplicate names so the hash map is smaller, just make this
179        // as big as package_data.
180        let mut by_package_name: AHashMap<String, Vec<Rc<PackageDataValue>>> =
181            AHashMap::with_capacity(all_package_data.len());
182        for package_data in all_package_data.values() {
183            by_package_name
184                .entry(package_data.name.clone())
185                .or_default()
186                .push(package_data.clone());
187        }
188
189        let resolve_data: AHashMap<_, _> = resolve_nodes
190            .into_iter()
191            .map(|node| {
192                (
193                    PackageId::from_metadata(node.id),
194                    // This used to return resolved features (node.features) as well but guppy
195                    // now does its own feature handling, so it isn't used any more.
196                    node.deps,
197                )
198            })
199            .collect();
200
201        Ok(Self {
202            dep_graph,
203            package_data: all_package_data,
204            by_package_name,
205            resolve_data,
206            workspace_root,
207            workspace_members,
208        })
209    }
210
211    fn process_package(
212        &mut self,
213        package: Package,
214    ) -> Result<(PackageId, PackageMetadataImpl), Box<Error>> {
215        let package_id = PackageId::from_metadata(package.id);
216        let (package_data, build_targets) =
217            self.package_data_and_remove_build_targets(&package_id)?;
218
219        let source = if self.workspace_members.contains(&package_id) {
220            PackageSourceImpl::Workspace(self.workspace_path(&package_id, &package.manifest_path)?)
221        } else if let Some(source) = package.source {
222            if source.is_crates_io() {
223                PackageSourceImpl::CratesIo
224            } else {
225                PackageSourceImpl::External(source.repr.into())
226            }
227        } else {
228            // Path dependency: get the directory from the manifest path.
229            let dirname = match package.manifest_path.parent() {
230                Some(dirname) => dirname,
231                None => {
232                    return Err(Error::PackageGraphConstructError(format!(
233                        "package '{}': manifest path '{}' does not have parent",
234                        package_id, package.manifest_path,
235                    ))
236                    .into());
237                }
238            };
239            PackageSourceImpl::create_path(dirname, self.workspace_root)
240        };
241
242        // resolved_deps is missing if the metadata was generated with --no-deps.
243        let resolved_deps = self.resolve_data.remove(&package_id).unwrap_or_default();
244
245        let dep_resolver = DependencyResolver::new(
246            &package_id,
247            &self.package_data,
248            &self.by_package_name,
249            &package.dependencies,
250        );
251
252        for NodeDep {
253            name: resolved_name,
254            pkg,
255            dep_kinds,
256            ..
257        } in resolved_deps
258        {
259            let dep_id = PackageId::from_metadata(pkg);
260            let (dep_data, deps) = dep_resolver.resolve(&resolved_name, &dep_id, &dep_kinds)?;
261            let link = PackageLinkImpl::new(&package_id, &resolved_name, deps)?;
262            // Use update_edge instead of add_edge to prevent multiple edges from being added
263            // between these two nodes.
264            // XXX maybe check for an existing edge?
265            self.dep_graph
266                .update_edge(package_data.package_ix, dep_data.package_ix, link);
267        }
268
269        let has_default_feature = package.features.contains_key("default");
270
271        // Optional dependencies could in principle be computed by looking at the edges out of this
272        // package, but unresolved dependencies aren't part of the graph so we're going to miss them
273        // (and many optional dependencies will be unresolved).
274        //
275        // XXX: Consider modeling unresolved dependencies in the graph.
276        //
277        // A dependency might be listed multiple times (e.g. as a build dependency and as a normal
278        // one). Some of them might be optional, some might not be. List a dependency here if *any*
279        // of those specifications are optional, since that's how Cargo features work. But also
280        // dedup them.
281        let optional_deps: IndexSet<_> = package
282            .dependencies
283            .into_iter()
284            .filter_map(|dep| {
285                if dep.optional {
286                    match dep.rename {
287                        Some(rename) => Some(rename.into_boxed_str()),
288                        None => Some(dep.name.into_boxed_str()),
289                    }
290                } else {
291                    None
292                }
293            })
294            .collect();
295
296        // Has the explicit feature by the name of this optional dep been seen?
297        let mut seen_explicit = FixedBitSet::with_capacity(optional_deps.len());
298
299        // The feature map contains both optional deps and named features.
300        let mut named_features: IndexMap<_, _> = package
301            .features
302            .into_iter()
303            .map(|(feature_name, deps)| {
304                let mut parsed_deps = SmallVec::with_capacity(deps.len());
305                for dep in deps {
306                    let dep = NamedFeatureDep::from_cargo_string(dep);
307                    if let NamedFeatureDep::OptionalDependency(d) = &dep {
308                        let index = optional_deps.get_index_of(d.as_ref()).ok_or_else(|| {
309                            Error::PackageGraphConstructError(format!(
310                                "package '{}': named feature {} specifies 'dep:{d}', but {d} is not an optional dependency",
311                                package_id,
312                                feature_name,
313                                d = d))
314                        })?;
315                        seen_explicit.set(index, true);
316                    }
317                    parsed_deps.push(dep);
318                }
319                Ok((feature_name.into_boxed_str(), parsed_deps))
320            })
321            .collect::<Result<_, Error>>()?;
322
323        // If an optional dependency was not seen explicitly, add an implicit named feature for it.
324        for (index, dep) in optional_deps.iter().enumerate() {
325            if !seen_explicit.contains(index) {
326                named_features.insert(
327                    dep.clone(),
328                    std::iter::once(NamedFeatureDep::OptionalDependency(dep.clone())).collect(),
329                );
330            }
331        }
332
333        // For compatibility with previous versions of guppy -- remove when a breaking change
334        // occurs.
335        let rust_version_req = package
336            .rust_version
337            .as_ref()
338            .map(|rust_version| VersionReq {
339                comparators: vec![semver::Comparator {
340                    op: semver::Op::GreaterEq,
341                    major: rust_version.major,
342                    minor: Some(rust_version.minor),
343                    patch: Some(rust_version.patch),
344                    // Rust versions don't support pre-release fields.
345                    pre: semver::Prerelease::EMPTY,
346                }],
347            });
348
349        Ok((
350            package_id,
351            PackageMetadataImpl {
352                name: package.name,
353                version: package.version,
354                authors: package.authors,
355                description: package.description.map(|s| s.into()),
356                license: package.license.map(|s| s.into()),
357                license_file: package.license_file.map(|f| f.into()),
358                manifest_path: package.manifest_path.into(),
359                categories: package.categories,
360                keywords: package.keywords,
361                readme: package.readme.map(|s| s.into()),
362                repository: package.repository.map(|s| s.into()),
363                homepage: package.homepage.map(|s| s.into()),
364                documentation: package.documentation.map(|s| s.into()),
365                edition: package.edition.to_string().into_boxed_str(),
366                metadata_table: package.metadata,
367                links: package.links.map(|s| s.into()),
368                publish: PackagePublishImpl::new(package.publish),
369                default_run: package.default_run.map(|s| s.into()),
370                rust_version: package.rust_version,
371                rust_version_req,
372                named_features,
373                optional_deps,
374
375                package_ix: package_data.package_ix,
376                source,
377                build_targets,
378                has_default_feature,
379            },
380        ))
381    }
382
383    fn package_data_and_remove_build_targets(
384        &self,
385        id: &PackageId,
386    ) -> Result<(Rc<PackageDataValue>, BuildTargetMap), Box<Error>> {
387        let package_data = self.package_data.get(id).ok_or_else(|| {
388            Error::PackageGraphConstructError(format!("no package data found for package '{}'", id))
389        })?;
390        let package_data = package_data.clone();
391        let build_targets = std::mem::take(&mut *package_data.build_targets.borrow_mut());
392        Ok((package_data, build_targets))
393    }
394
395    /// Computes the relative path from workspace root to this package, which might be out of root sub tree.
396    fn workspace_path(
397        &self,
398        id: &PackageId,
399        manifest_path: &Utf8Path,
400    ) -> Result<Box<Utf8Path>, Box<Error>> {
401        // Get relative path from workspace root to manifest path.
402        let workspace_path = pathdiff::diff_utf8_paths(manifest_path, self.workspace_root)
403            .ok_or_else(|| {
404                Error::PackageGraphConstructError(format!(
405                    "failed to find path from workspace (root: {}) to member '{id}' at path {manifest_path}",
406                    self.workspace_root
407                ))
408            })?;
409        let workspace_path = workspace_path.parent().ok_or_else(|| {
410            Error::PackageGraphConstructError(format!(
411                "workspace member '{}' has invalid manifest path {:?}",
412                id, manifest_path
413            ))
414        })?;
415        Ok(convert_relative_forward_slashes(workspace_path).into_boxed_path())
416    }
417
418    fn finish(self) -> Graph<PackageId, PackageLinkImpl, Directed, PackageIx> {
419        self.dep_graph
420    }
421}
422
423/// Intermediate state for a package as stored in `GraphBuildState`.
424#[derive(Debug)]
425struct PackageDataValue {
426    package_ix: NodeIndex<PackageIx>,
427    name: String,
428    resolved_name: ResolvedName,
429    // build_targets is used in two spots: in the constructor here, and removed from this field in
430    // package_data_and_remove_build_targets.
431    build_targets: RefCell<BuildTargetMap>,
432    version: Version,
433}
434
435impl PackageDataValue {
436    fn new(
437        package: &mut Package,
438        dep_graph: &mut Graph<PackageId, PackageLinkImpl, Directed, PackageIx>,
439    ) -> Result<(PackageId, Rc<Self>), Box<Error>> {
440        let package_id = PackageId::from_metadata(package.id.clone());
441        let package_ix = dep_graph.add_node(package_id.clone());
442
443        // Build up the list of build targets -- this will be used to construct the resolved_name.
444        let mut build_targets = BuildTargets::new(&package_id);
445        for build_target in package.targets.drain(..) {
446            build_targets.add(build_target)?;
447        }
448        let build_targets = build_targets.finish();
449
450        let resolved_name = match build_targets.get(&OwnedBuildTargetId::Library) {
451            Some(target) => {
452                let lib_name = target
453                    .lib_name
454                    .as_deref()
455                    .expect("lib_name is always specified for library targets");
456                if lib_name != package.name {
457                    ResolvedName::LibNameSpecified(lib_name.to_string())
458                } else {
459                    // The resolved name is the same as the package name.
460                    ResolvedName::LibNameNotSpecified(lib_name.replace('-', "_"))
461                }
462            }
463            None => {
464                // This means that it's a weird case like a binary-only dependency (not part of
465                // stable Rust as of 2023-11). This will typically be reflected as an empty resolved
466                // name.
467                ResolvedName::NoLibTarget
468            }
469        };
470
471        let value = PackageDataValue {
472            package_ix,
473            name: package.name.clone(),
474            resolved_name,
475            build_targets: RefCell::new(build_targets),
476            version: package.version.clone(),
477        };
478
479        Ok((package_id, Rc::new(value)))
480    }
481}
482
483#[derive(Clone, Debug, Eq, PartialEq, Hash)]
484enum ResolvedName {
485    LibNameSpecified(String),
486    /// This variant has its - replaced with _.
487    LibNameNotSpecified(String),
488    NoLibTarget,
489}
490
491/// Matcher for the resolved name of a dependency.
492///
493/// The "rename" field in a dependency, if present, is generally used. (But not always! There are
494/// cases where even if a rename is present, the package name is used instead.)
495#[derive(Clone, Debug, Eq, PartialEq, Hash)]
496struct ReqResolvedName<'g> {
497    // A renamed name, if any.
498    renamed: Option<String>,
499
500    // A resolved name created from the lib.name field.
501    resolved_name: &'g ResolvedName,
502}
503
504impl<'g> ReqResolvedName<'g> {
505    fn new(renamed: Option<&str>, resolved_name: &'g ResolvedName) -> Self {
506        Self {
507            renamed: renamed.map(|s| s.replace('-', "_")),
508            resolved_name,
509        }
510    }
511
512    fn matches(&self, name: &str) -> bool {
513        if let Some(rename) = &self.renamed {
514            if rename == name {
515                return true;
516            }
517        }
518
519        match self.resolved_name {
520            ResolvedName::LibNameSpecified(resolved_name) => *resolved_name == name,
521            ResolvedName::LibNameNotSpecified(resolved_name) => *resolved_name == name,
522            ResolvedName::NoLibTarget => {
523                // This code path is only hit with nightly Rust as of 2023-11. It depends on Rust
524                // RFC 3028. at https://github.com/rust-lang/cargo/issues/9096.
525                //
526                // This isn't quite right -- if we have two or more non-lib dependencies, we'll
527                // return true for both of them over here. What we need to do instead is use the
528                // extern_name and bin_name fields that are present in nightly DepKindInfo, but that
529                // aren't in stable yet. For now, this is the best we can do.
530                //
531                // (If we're going to be relying on heuristics, it is also possible to use the
532                // package ID over here, but that's documented to be an opaque string. It also
533                // wouldn't be resilient to patch and replace.)
534                name.is_empty()
535            }
536        }
537    }
538}
539
540impl PackageSourceImpl {
541    fn create_path(path: &Utf8Path, workspace_root: &Utf8Path) -> Self {
542        let path_diff =
543            pathdiff::diff_utf8_paths(path, workspace_root).expect("workspace root is absolute");
544        // convert_relative_forward_slashes() can handle both situations, i.e.,
545        // on windows and for relative path, convert forward slashes, otherwise,
546        // (absolute path, or not on windows) just clone.
547        Self::Path(convert_relative_forward_slashes(path_diff).into_boxed_path())
548    }
549}
550
551impl NamedFeatureDep {
552    fn from_cargo_string(input: impl Into<String>) -> Self {
553        let input = input.into();
554        match input.split_once('/') {
555            Some((dep_name, feature)) => {
556                if let Some(dep_name_without_q) = dep_name.strip_suffix('?') {
557                    Self::dep_named_feature(dep_name_without_q, feature, true)
558                } else {
559                    Self::dep_named_feature(dep_name, feature, false)
560                }
561            }
562            None => match input.strip_prefix("dep:") {
563                Some(dep_name) => Self::optional_dependency(dep_name),
564                None => Self::named_feature(input),
565            },
566        }
567    }
568}
569
570type BuildTargetMap = BTreeMap<OwnedBuildTargetId, BuildTargetImpl>;
571
572struct BuildTargets<'a> {
573    package_id: &'a PackageId,
574    targets: BuildTargetMap,
575}
576
577impl<'a> BuildTargets<'a> {
578    fn new(package_id: &'a PackageId) -> Self {
579        Self {
580            package_id,
581            targets: BTreeMap::new(),
582        }
583    }
584
585    fn add(&mut self, target: Target) -> Result<(), Box<Error>> {
586        use std::collections::btree_map::Entry;
587
588        // Figure out the id and kind using target.kind and target.crate_types.
589        let mut target_kinds = target
590            .kind
591            .into_iter()
592            .map(|kind| kind.to_string())
593            .collect::<Vec<_>>();
594        let target_name = target.name.into_boxed_str();
595        // Store crate types as strings to avoid exposing cargo_metadata in the
596        // public API.
597        let crate_types = SortedSet::new(
598            target
599                .crate_types
600                .into_iter()
601                .map(|ct| ct.to_string())
602                .collect::<Vec<_>>(),
603        );
604
605        // The "proc-macro" crate type cannot mix with any other types or kinds.
606        if target_kinds.len() > 1 && Self::is_proc_macro(&target_kinds) {
607            return Err(Error::PackageGraphConstructError(format!(
608                "for package {}, proc-macro mixed with other kinds ({:?})",
609                self.package_id, target_kinds
610            ))
611            .into());
612        }
613        if crate_types.len() > 1 && Self::is_proc_macro(&crate_types) {
614            return Err(Error::PackageGraphConstructError(format!(
615                "for package {}, proc-macro mixed with other crate types ({})",
616                self.package_id, crate_types
617            ))
618            .into());
619        }
620
621        let (id, kind, lib_name) = if target_kinds.len() > 1 {
622            // multiple kinds always means a library target.
623            (
624                OwnedBuildTargetId::Library,
625                BuildTargetKindImpl::LibraryOrExample(crate_types),
626                Some(target_name),
627            )
628        } else if let Some(target_kind) = target_kinds.pop() {
629            let (id, lib_name) = match target_kind.as_str() {
630                "custom-build" => (OwnedBuildTargetId::BuildScript, Some(target_name)),
631                "bin" => (OwnedBuildTargetId::Binary(target_name), None),
632                "example" => (OwnedBuildTargetId::Example(target_name), None),
633                "test" => (OwnedBuildTargetId::Test(target_name), None),
634                "bench" => (OwnedBuildTargetId::Benchmark(target_name), None),
635                _other => {
636                    // Assume that this is a library crate.
637                    (OwnedBuildTargetId::Library, Some(target_name))
638                }
639            };
640
641            let kind = match &id {
642                OwnedBuildTargetId::Library => {
643                    if crate_types.as_slice() == ["proc-macro"] {
644                        BuildTargetKindImpl::ProcMacro
645                    } else {
646                        BuildTargetKindImpl::LibraryOrExample(crate_types)
647                    }
648                }
649                OwnedBuildTargetId::Example(_) => {
650                    BuildTargetKindImpl::LibraryOrExample(crate_types)
651                }
652                _ => {
653                    // The crate_types must be exactly "bin".
654                    if crate_types.as_slice() != ["bin"] {
655                        return Err(Error::PackageGraphConstructError(format!(
656                            "for package {}: build target '{:?}' has invalid crate types '{}'",
657                            self.package_id, id, crate_types,
658                        ))
659                        .into());
660                    }
661                    BuildTargetKindImpl::Binary
662                }
663            };
664
665            (id, kind, lib_name)
666        } else {
667            return Err(Error::PackageGraphConstructError(format!(
668                "for package ID '{}': build target '{}' has no kinds",
669                self.package_id, target_name
670            ))
671            .into());
672        };
673
674        match self.targets.entry(id) {
675            Entry::Occupied(occupied) => {
676                return Err(Error::PackageGraphConstructError(format!(
677                    "for package ID '{}': duplicate build targets for {:?}",
678                    self.package_id,
679                    occupied.key()
680                ))
681                .into());
682            }
683            Entry::Vacant(vacant) => {
684                vacant.insert(BuildTargetImpl {
685                    kind,
686                    lib_name,
687                    required_features: target.required_features,
688                    path: target.src_path.into_boxed_path(),
689                    edition: target.edition.to_string().into_boxed_str(),
690                    doc_by_default: target.doc,
691                    doctest_by_default: target.doctest,
692                    test_by_default: target.test,
693                });
694            }
695        }
696
697        Ok(())
698    }
699
700    fn is_proc_macro(list: &[String]) -> bool {
701        list.iter().any(|kind| *kind == "proc-macro")
702    }
703
704    fn finish(self) -> BuildTargetMap {
705        self.targets
706    }
707}
708
709struct DependencyResolver<'g> {
710    from_id: &'g PackageId,
711
712    /// The package data, inherited from the graph build state.
713    package_data: &'g AHashMap<PackageId, Rc<PackageDataValue>>,
714
715    /// This is a list of dependency requirements. We don't know the package ID yet so we don't have
716    /// a great key to work with. This could be improved in the future by matching on requirements
717    /// (though it's hard).
718    dep_reqs: DependencyReqs<'g>,
719}
720
721impl<'g> DependencyResolver<'g> {
722    /// Constructs a new resolver using the provided package data and dependencies.
723    fn new(
724        from_id: &'g PackageId,
725        package_data: &'g AHashMap<PackageId, Rc<PackageDataValue>>,
726        by_package_name: &'g AHashMap<String, Vec<Rc<PackageDataValue>>>,
727        package_deps: impl IntoIterator<Item = &'g Dependency>,
728    ) -> Self {
729        let mut dep_reqs = DependencyReqs::default();
730        for dep in package_deps {
731            // Determine what the resolved name of each package could be by matching on package name
732            // and version (NOT source, because the source can be patched).
733            let Some(packages) = by_package_name.get(&dep.name) else {
734                // This dependency did not lead to a resolved package.
735                continue;
736            };
737            for package in packages {
738                if cargo_version_matches(&dep.req, &package.version) {
739                    // The cargo `resolve.deps` map uses one of two things:
740                    //
741                    // 1. dep.rename with - turned into _, if specified.
742                    // 2. lib.name, if specified, otherwise package.name with - turned into _.
743                    //
744                    // ReqResolvedName tracks both of these.
745                    let req_resolved_name =
746                        ReqResolvedName::new(dep.rename.as_deref(), &package.resolved_name);
747                    dep_reqs.push(req_resolved_name, dep);
748                }
749            }
750        }
751
752        Self {
753            from_id,
754            package_data,
755            dep_reqs,
756        }
757    }
758
759    /// Resolves this dependency by finding the `Dependency` items corresponding to this resolved
760    /// name and package ID.
761    fn resolve<'a>(
762        &'a self,
763        resolved_name: &'a str,
764        dep_id: &PackageId,
765        dep_kinds: &'a [DepKindInfo],
766    ) -> Result<
767        (
768            &'g Rc<PackageDataValue>,
769            impl Iterator<Item = &'g Dependency> + 'a,
770        ),
771        Error,
772    > {
773        let dep_data = self.package_data.get(dep_id).ok_or_else(|| {
774            Error::PackageGraphConstructError(format!(
775                "{}: no package data found for dependency '{}'",
776                self.from_id, dep_id
777            ))
778        })?;
779
780        Ok((
781            dep_data,
782            self.dep_reqs
783                .matches_for(resolved_name, dep_data, dep_kinds),
784        ))
785    }
786}
787
788/// Maintains a list of dependency requirements to match up to for a given package name.
789#[derive(Clone, Debug, Default)]
790struct DependencyReqs<'g> {
791    // The keys are (resolved name, dependency).
792    reqs: Vec<(ReqResolvedName<'g>, &'g Dependency)>,
793}
794
795impl<'g> DependencyReqs<'g> {
796    fn push(&mut self, resolved_name: ReqResolvedName<'g>, dependency: &'g Dependency) {
797        self.reqs.push((resolved_name, dependency));
798    }
799
800    fn matches_for<'a>(
801        &'a self,
802        resolved_name: &'a str,
803        package_data: &'a PackageDataValue,
804        dep_kinds: &'a [DepKindInfo],
805    ) -> impl Iterator<Item = &'g Dependency> + 'a {
806        self.reqs
807            .iter()
808            .filter_map(move |(req_resolved_name, dep)| {
809                // A dependency requirement matches this package if all of the following are true:
810                //
811                // 1. The resolved_name matches.
812                // 2. The Cargo version matches (XXX is this necessary?)
813                // 3. The dependency kind and target is found in dep_kinds.
814                if !req_resolved_name.matches(resolved_name) {
815                    return None;
816                }
817
818                if !cargo_version_matches(&dep.req, &package_data.version) {
819                    return None;
820                }
821
822                // Some older manifests don't have the dep_kinds field -- in that case we can't
823                // fully match manifests and just accept all such packages. We just can't do better
824                // than that.
825                if dep_kinds.is_empty() {
826                    return Some(*dep);
827                }
828
829                dep_kinds
830                    .iter()
831                    .any(|dep_kind| dep_kind.kind == dep.kind && dep_kind.target == dep.target)
832                    .then_some(*dep)
833            })
834    }
835}
836
837impl PackageLinkImpl {
838    fn new<'a>(
839        from_id: &PackageId,
840        resolved_name: &str,
841        deps: impl IntoIterator<Item = &'a Dependency>,
842    ) -> Result<Self, Box<Error>> {
843        let mut version_req = None;
844        let mut normal = DependencyReqImpl::default();
845        let mut build = DependencyReqImpl::default();
846        let mut dev = DependencyReqImpl::default();
847
848        // We hope that the dep name is the same for all of these, but it's not guaranteed.
849        let mut dep_name: Option<String> = None;
850        for dep in deps {
851            let rename_or_name = dep.rename.as_ref().unwrap_or(&dep.name);
852            match &dep_name {
853                Some(dn) => {
854                    if dn != rename_or_name {
855                        // XXX: warn or error on this?
856                    }
857                }
858                None => {
859                    dep_name = Some(rename_or_name.clone());
860                }
861            }
862
863            // Dev dependencies cannot be optional.
864            if dep.kind == DependencyKind::Development && dep.optional {
865                return Err(Error::PackageGraphConstructError(format!(
866                    "for package '{}': dev-dependency '{}' marked optional",
867                    from_id,
868                    dep_name.expect("dep_name set above"),
869                ))
870                .into());
871            }
872
873            // Pick the first version req that this come across.
874            if version_req.is_none() {
875                version_req = Some(dep.req.clone());
876            }
877
878            match dep.kind {
879                DependencyKind::Normal => normal.add_instance(from_id, dep)?,
880                DependencyKind::Build => build.add_instance(from_id, dep)?,
881                DependencyKind::Development => dev.add_instance(from_id, dep)?,
882                _ => {
883                    // unknown dependency kind -- can't do much with this!
884                    continue;
885                }
886            };
887        }
888
889        let dep_name = dep_name.ok_or_else(|| {
890            Error::PackageGraphConstructError(format!(
891                "for package '{}': no dependencies found matching '{}'",
892                from_id, resolved_name,
893            ))
894        })?;
895        let version_req = version_req.unwrap_or_else(|| {
896            panic!(
897                "requires at least one dependency instance: \
898                 from `{from_id}` to `{dep_name}` (resolved name `{resolved_name}`)"
899            )
900        });
901
902        Ok(Self {
903            dep_name,
904            resolved_name: resolved_name.into(),
905            version_req,
906            normal,
907            build,
908            dev,
909        })
910    }
911}
912
913/// It is possible to specify a dependency several times within the same section through
914/// platform-specific dependencies and the [target] section. For example:
915/// https://github.com/alexcrichton/flate2-rs/blob/5751ad9/Cargo.toml#L29-L33
916///
917/// ```toml
918/// [dependencies]
919/// miniz_oxide = { version = "0.3.2", optional = true}
920///
921/// [target.'cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))'.dependencies]
922/// miniz_oxide = "0.3.2"
923/// ```
924///
925/// (From here on, each separate time a particular version of a dependency
926/// is listed, it is called an "instance".)
927///
928/// For such situations, there are two separate analyses that happen:
929///
930/// 1. Whether the dependency is included at all. This is a union of all instances, conditional on
931///    the specifics of the `[target]` lines.
932/// 2. What features are enabled. As of cargo 1.42, this is unified across all instances but
933///    separately for required/optional instances.
934///
935/// Note that the new feature resolver
936/// (https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#features)'s `itarget` setting
937/// causes this union-ing to *not* happen, so that's why we store all the features enabled by
938/// each target separately.
939impl DependencyReqImpl {
940    fn add_instance(&mut self, from_id: &PackageId, dep: &Dependency) -> Result<(), Box<Error>> {
941        if dep.optional {
942            self.optional.add_instance(from_id, dep)
943        } else {
944            self.required.add_instance(from_id, dep)
945        }
946    }
947}
948
949impl DepRequiredOrOptional {
950    fn add_instance(&mut self, from_id: &PackageId, dep: &Dependency) -> Result<(), Box<Error>> {
951        // target_spec is None if this is not a platform-specific dependency.
952        let target_spec = match dep.target.as_ref() {
953            Some(spec_or_triple) => {
954                // This is a platform-specific dependency, so add it to the list of specs.
955                let spec_or_triple = format!("{}", spec_or_triple);
956                let target_spec: TargetSpec = spec_or_triple.parse().map_err(|err| {
957                    Error::PackageGraphConstructError(format!(
958                        "for package '{}': for dependency '{}', parsing target '{}' failed: {}",
959                        from_id, dep.name, spec_or_triple, err
960                    ))
961                })?;
962                Some(target_spec)
963            }
964            None => None,
965        };
966
967        self.build_if.add_spec(target_spec.as_ref());
968        if dep.uses_default_features {
969            self.default_features_if.add_spec(target_spec.as_ref());
970        } else {
971            self.no_default_features_if.add_spec(target_spec.as_ref());
972        }
973
974        for feature in &dep.features {
975            self.feature_targets
976                .entry(feature.clone())
977                .or_default()
978                .add_spec(target_spec.as_ref());
979        }
980        Ok(())
981    }
982}
983
984impl PackagePublishImpl {
985    /// Converts cargo_metadata registries to our own format.
986    fn new(registries: Option<Vec<String>>) -> Self {
987        match registries {
988            None => PackagePublishImpl::Unrestricted,
989            Some(registries) => PackagePublishImpl::Registries(registries.into_boxed_slice()),
990        }
991    }
992}
993
994/// Replace backslashes in a relative path with forward slashes on Windows.
995#[track_caller]
996fn convert_relative_forward_slashes<'a>(rel_path: impl Into<Cow<'a, Utf8Path>>) -> Utf8PathBuf {
997    let rel_path = rel_path.into();
998    cfg_if::cfg_if! { if #[cfg(windows)] {
999
1000        if rel_path.is_relative() {
1001            rel_path.as_str().replace("\\", "/").into()
1002        } else {
1003            rel_path.into_owned()
1004        }
1005
1006    } else {
1007
1008        rel_path.into_owned()
1009
1010    }}
1011}
1012
1013#[cfg(test)]
1014mod tests {
1015    use super::*;
1016
1017    #[test]
1018    fn test_parse_named_feature_dependency() {
1019        assert_eq!(
1020            NamedFeatureDep::from_cargo_string("dep/bar"),
1021            NamedFeatureDep::dep_named_feature("dep", "bar", false),
1022        );
1023        assert_eq!(
1024            NamedFeatureDep::from_cargo_string("dep?/bar"),
1025            NamedFeatureDep::dep_named_feature("dep", "bar", true),
1026        );
1027        assert_eq!(
1028            NamedFeatureDep::from_cargo_string("dep:bar"),
1029            NamedFeatureDep::optional_dependency("bar"),
1030        );
1031        assert_eq!(
1032            NamedFeatureDep::from_cargo_string("foo-bar"),
1033            NamedFeatureDep::named_feature("foo-bar"),
1034        );
1035    }
1036
1037    #[test]
1038    fn test_create_path() {
1039        assert_eq!(
1040            PackageSourceImpl::create_path("/data/foo".as_ref(), "/data/bar".as_ref()),
1041            PackageSourceImpl::Path("../foo".into())
1042        );
1043        assert_eq!(
1044            PackageSourceImpl::create_path("/tmp/foo".as_ref(), "/data/bar".as_ref()),
1045            PackageSourceImpl::Path("../../tmp/foo".into())
1046        );
1047    }
1048
1049    #[test]
1050    fn test_convert_relative_forward_slashes() {
1051        let components = vec!["..", "..", "foo", "bar", "baz.txt"];
1052        let path: Utf8PathBuf = components.into_iter().collect();
1053        let path = convert_relative_forward_slashes(path);
1054        // This should have forward-slashes, even on Windows.
1055        assert_eq!(path.as_str(), "../../foo/bar/baz.txt");
1056    }
1057
1058    #[track_caller]
1059    fn verify_result_of_diff_utf8_paths(
1060        path_manifest: &str,
1061        path_workspace_root: &str,
1062        expected_relative_path: &str,
1063    ) {
1064        let relative_path = pathdiff::diff_utf8_paths(
1065            Utf8Path::new(path_manifest),
1066            Utf8Path::new(path_workspace_root),
1067        )
1068        .unwrap();
1069        assert_eq!(relative_path, expected_relative_path);
1070    }
1071
1072    #[test]
1073    fn test_workspace_path_out_of_pocket() {
1074        verify_result_of_diff_utf8_paths(
1075            "/workspace/a/b/Crate/Cargo.toml",
1076            "/workspace/a/b/.cargo/workspace",
1077            r"../../Crate/Cargo.toml",
1078        );
1079    }
1080
1081    #[cfg(windows)] // Test for '\\' and 'X:\' etc on windows
1082    mod windows {
1083        use super::*;
1084
1085        #[test]
1086        fn test_create_path_windows() {
1087            // Ensure that relative paths are stored with forward slashes.
1088            assert_eq!(
1089                PackageSourceImpl::create_path("C:\\data\\foo".as_ref(), "C:\\data\\bar".as_ref()),
1090                PackageSourceImpl::Path("../foo".into())
1091            );
1092            // Paths that span drives cannot be stored as relative.
1093            assert_eq!(
1094                PackageSourceImpl::create_path("D:\\tmp\\foo".as_ref(), "C:\\data\\bar".as_ref()),
1095                PackageSourceImpl::Path("D:\\tmp\\foo".into())
1096            );
1097        }
1098
1099        #[test]
1100        fn test_convert_relative_forward_slashes_absolute() {
1101            let components = vec![r"D:\", "X", "..", "foo", "bar", "baz.txt"];
1102            let path: Utf8PathBuf = components.into_iter().collect();
1103            let path = convert_relative_forward_slashes(path);
1104            // Absolute path keep using backslash on Windows.
1105            assert_eq!(path.as_str(), r"D:\X\..\foo\bar\baz.txt");
1106        }
1107
1108        #[test]
1109        fn test_workspace_path_out_of_pocket_on_windows_same_driver() {
1110            verify_result_of_diff_utf8_paths(
1111                r"C:\workspace\a\b\Crate\Cargo.toml",
1112                r"C:\workspace\a\b\.cargo\workspace",
1113                r"..\..\Crate\Cargo.toml",
1114            );
1115        }
1116
1117        #[test]
1118        fn test_workspace_path_out_of_pocket_on_windows_different_driver() {
1119            verify_result_of_diff_utf8_paths(
1120                r"D:\workspace\a\b\Crate\Cargo.toml",
1121                r"C:\workspace\a\b\.cargo\workspace",
1122                r"D:\workspace\a\b\Crate\Cargo.toml",
1123            );
1124        }
1125    }
1126}