Skip to main content

zccache_depgraph/
search_paths.rs

1//! Include search path types and resolution order.
2
3use std::path::Path;
4
5use zccache_core::NormalizedPath;
6
7/// Ordered include search paths, preserving -I/-isystem/-iquote/-idirafter.
8///
9/// Resolution order for `#include "foo.h"` (quoted):
10///   1. Directory of the including file
11///   2. `-iquote` dirs
12///   3. `-I` dirs
13///   4. `-isystem` dirs + compiler defaults
14///   5. `-idirafter` dirs
15///
16/// Resolution order for `#include <foo.h>` (angle bracket):
17///   1. `-I` dirs
18///   2. `-isystem` dirs + compiler defaults
19///   3. `-idirafter` dirs
20#[derive(Debug, Clone, Default)]
21pub struct IncludeSearchPaths {
22    /// `-iquote` paths — searched only for quoted includes, before `-I`.
23    pub iquote: Vec<NormalizedPath>,
24    /// `-I` paths — user include paths (order matters!).
25    pub user: Vec<NormalizedPath>,
26    /// `-isystem` paths + compiler default system dirs.
27    pub system: Vec<NormalizedPath>,
28    /// `-idirafter` paths — searched last.
29    pub after: Vec<NormalizedPath>,
30}
31
32impl IncludeSearchPaths {
33    /// Iterate search dirs for a quoted include (`#include "foo.h"`),
34    /// starting after the including file's own directory.
35    pub fn quoted_search_dirs(&self) -> impl Iterator<Item = &Path> {
36        self.iquote
37            .iter()
38            .chain(self.user.iter())
39            .chain(self.system.iter())
40            .chain(self.after.iter())
41            .map(|p| p.as_path())
42    }
43
44    /// Iterate search dirs for an angle-bracket include (`#include <foo.h>`).
45    pub fn angle_search_dirs(&self) -> impl Iterator<Item = &Path> {
46        self.user
47            .iter()
48            .chain(self.system.iter())
49            .chain(self.after.iter())
50            .map(|p| p.as_path())
51    }
52
53    /// Iterate all search dirs in priority order (iquote → user → system → after).
54    /// This is the superset of both quoted and angle-bracket search orders.
55    pub fn all_search_dirs(&self) -> impl Iterator<Item = &Path> {
56        self.iquote
57            .iter()
58            .chain(self.user.iter())
59            .chain(self.system.iter())
60            .chain(self.after.iter())
61            .map(|p| p.as_path())
62    }
63}
64
65#[cfg(test)]
66mod tests {
67    use super::*;
68
69    #[test]
70    fn quoted_search_includes_iquote_first() {
71        let paths = IncludeSearchPaths {
72            iquote: vec!["/q".into()],
73            user: vec!["/u".into()],
74            system: vec!["/s".into()],
75            after: vec!["/a".into()],
76        };
77        let dirs: Vec<&Path> = paths.quoted_search_dirs().collect();
78        assert_eq!(
79            dirs,
80            vec![
81                Path::new("/q"),
82                Path::new("/u"),
83                Path::new("/s"),
84                Path::new("/a"),
85            ]
86        );
87    }
88
89    #[test]
90    fn angle_search_skips_iquote() {
91        let paths = IncludeSearchPaths {
92            iquote: vec!["/q".into()],
93            user: vec!["/u".into()],
94            system: vec!["/s".into()],
95            after: vec!["/a".into()],
96        };
97        let dirs: Vec<&Path> = paths.angle_search_dirs().collect();
98        assert_eq!(
99            dirs,
100            vec![Path::new("/u"), Path::new("/s"), Path::new("/a"),]
101        );
102    }
103
104    #[test]
105    fn empty_paths_produce_empty_iterators() {
106        let paths = IncludeSearchPaths::default();
107        assert_eq!(paths.quoted_search_dirs().count(), 0);
108        assert_eq!(paths.angle_search_dirs().count(), 0);
109    }
110
111    #[test]
112    fn user_dir_order_preserved() {
113        let paths = IncludeSearchPaths {
114            user: vec!["/first".into(), "/second".into(), "/third".into()],
115            ..Default::default()
116        };
117        let dirs: Vec<&Path> = paths.angle_search_dirs().collect();
118        assert_eq!(
119            dirs,
120            vec![
121                Path::new("/first"),
122                Path::new("/second"),
123                Path::new("/third")
124            ]
125        );
126    }
127}