• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

kaspar030 / laze / 19943262596

04 Dec 2025 08:41PM UTC coverage: 80.01% (-0.2%) from 80.256%
19943262596

push

github

web-flow
feat: introduce `requires` (#820)

75 of 116 new or added lines in 6 files covered. (64.66%)

1 existing line in 1 file now uncovered.

3366 of 4207 relevant lines covered (80.01%)

97.54 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

83.02
/src/model/module.rs
1
use std::collections::{HashMap, HashSet};
2
use std::fmt;
3
use std::hash::{Hash, Hasher};
4

5
use anyhow::Error;
6
use camino::{Utf8Path, Utf8PathBuf};
7
use indexmap::{indexset, IndexMap, IndexSet};
8
use serde::{Deserialize, Serialize};
9

10
use crate::build::ResolverResult;
11
use crate::download;
12
use crate::nested_env;
13
use crate::nested_env::Env;
14
use crate::Dependency;
15

16
use super::Task;
17

18
#[derive(Clone, Eq, Debug, Default)]
19
pub struct Module {
20
    pub name: String,
21
    pub context_name: String,
22

23
    pub help: Option<String>,
24

25
    pub selects: Vec<Dependency<String>>,
26
    pub imports: Vec<Dependency<String>>,
27
    pub provides: Option<Vec<String>>,
28
    pub conflicts: Option<Vec<String>>,
29
    pub requires: Option<Vec<String>>,
30

31
    pub notify_all: bool,
32

33
    pub blocklist: Option<Vec<String>>,
34
    pub allowlist: Option<Vec<String>>,
35

36
    pub sources: Vec<String>,
37
    pub sources_optional: Option<IndexMap<String, Vec<String>>>,
38

39
    pub tasks: HashMap<String, Task>,
40

41
    pub build: Option<CustomBuild>,
42

43
    pub env_local: Env,
44
    pub env_export: Env,
45
    pub env_global: Env,
46
    pub env_early: Env,
47

48
    pub download: Option<download::Download>,
49
    pub context_id: Option<usize>,
50
    pub defined_in: Option<Utf8PathBuf>,
51
    pub relpath: Option<Utf8PathBuf>,
52
    pub srcdir: Option<Utf8PathBuf>,
53
    pub build_dep_files: Option<IndexSet<Utf8PathBuf>>,
54
    pub is_build_dep: bool,
55
    pub is_global_build_dep: bool,
56
    pub is_binary: bool,
57
}
58

59
impl Module {
60
    pub fn new(name: String, context_name: Option<String>) -> Module {
199✔
61
        Module {
62
            name,
199✔
63
            context_name: context_name.unwrap_or_else(|| "default".to_string()),
199✔
64
            ..Default::default()
199✔
65
        }
66
    }
199✔
67

68
    pub fn from(defaults: &Module, name: String, context_name: Option<String>) -> Module {
5✔
69
        Module {
70
            name,
5✔
71
            context_name: context_name.unwrap_or_else(|| defaults.context_name.clone()),
5✔
72
            ..defaults.clone()
5✔
73
        }
74
    }
5✔
75

76
    //fn can_build_for(&self, context: &Context, contexts: &ContextBag) -> bool {
77
    //    contexts.context_is_in(self.context_id.unwrap(), context.index.unwrap())
78
    //}
79
    //
80
    fn get_imports_recursive<'a, 'b>(
377✔
81
        &'a self,
377✔
82
        modules: &IndexMap<&'a String, &'a Module>,
377✔
83
        providers: &IndexMap<&'a String, Vec<&'a Module>>,
377✔
84
        seen: Option<&mut HashSet<&'b String>>,
377✔
85
    ) -> Vec<&'a Module>
377✔
86
    where
377✔
87
        'a: 'b,
377✔
88
    {
89
        let mut result = Vec::new();
377✔
90

91
        let mut newseen = HashSet::new();
377✔
92
        let seen = match seen {
377✔
93
            Some(seen) => seen,
84✔
94
            None => &mut newseen,
293✔
95
        };
96

97
        let module = self;
377✔
98
        if seen.contains(&self.name) {
377✔
99
            return Vec::new();
1✔
100
        }
376✔
101
        seen.insert(&self.name);
376✔
102

103
        for dep in &module.imports {
376✔
104
            let dep_name = match dep {
94✔
105
                Dependency::Hard(name) => name,
79✔
106
                Dependency::Soft(name) => name,
10✔
107
                Dependency::IfThenHard(other, name) => {
5✔
108
                    if modules.contains_key(other) {
5✔
109
                        name
5✔
110
                    } else {
111
                        continue;
×
112
                    }
113
                }
114
                Dependency::IfThenSoft(other, name) => {
×
115
                    if modules.contains_key(other) {
×
116
                        name
×
117
                    } else {
118
                        continue;
×
119
                    }
120
                }
121
            };
122

123
            // this recurses into the dependency
124
            if let Some(other_module) = modules.get(&dep_name) {
94✔
125
                let mut other_deps =
84✔
126
                    other_module.get_imports_recursive(modules, providers, Some(seen));
84✔
127
                result.append(&mut other_deps);
84✔
128
            }
84✔
129

130
            // this recurses into all modules that "provide" this dependency
131
            if let Some(providing_modules) = providers.get(&dep_name) {
94✔
132
                for provider in providing_modules {
×
133
                    debug_assert!(modules.contains_key(&provider.name));
×
134
                    let mut provider_deps =
×
135
                        provider.get_imports_recursive(modules, providers, Some(seen));
×
136
                    result.append(&mut provider_deps);
×
137
                }
138
            }
94✔
139
        }
140

141
        result.push(self);
376✔
142

143
        result
376✔
144
    }
377✔
145

146
    pub fn build_env<'a>(
293✔
147
        &'a self,
293✔
148
        global_env: &Env,
293✔
149
        resolver_result: &'a ResolverResult,
293✔
150
    ) -> (Env, Option<IndexSet<&'a Module>>) {
293✔
151
        let modules = &resolver_result.modules;
293✔
152
        let providers = &resolver_result.providers;
293✔
153

154
        /* start with the global env env */
155
        let mut module_env = global_env.clone();
293✔
156

157
        /* collect this module's module build deps */
158
        let mut build_dep_modules = None;
293✔
159

160
        /* from each (recursive) import ... */
161
        let deps = self.get_imports_recursive(modules, providers, None);
293✔
162

163
        for dep in &deps {
376✔
164
            /* merge that dependency's exported env */
165
            module_env.merge(&dep.env_export);
376✔
166

167
            // add all *imported (used)* dependencies to this modules "notify" env var
168
            // (unless it has "notify_all" set, we'll handle that later)
169
            if !self.notify_all {
376✔
170
                let notify_list = module_env
375✔
171
                    .entry("notify".into())
375✔
172
                    .or_insert_with(|| nested_env::EnvKey::List(im::vector![]));
375✔
173

174
                match notify_list {
375✔
175
                    nested_env::EnvKey::Single(_) => panic!("unexpected notify value"),
×
176
                    nested_env::EnvKey::List(list) => list.push_back(dep.create_module_define()),
375✔
177
                }
178
            }
1✔
179

180
            // collect all imported file build dependencies
181
            if dep != &self && dep.is_build_dep {
376✔
182
                build_dep_modules
6✔
183
                    .get_or_insert_with(IndexSet::new)
6✔
184
                    .insert(*dep);
6✔
185
            }
370✔
186
        }
187

188
        // add *all* modules to this modules "notify" env var if requested
189
        if self.notify_all {
293✔
190
            let all_modules = modules
1✔
191
                .iter()
1✔
192
                .filter(|(_, dep)| !dep.is_context_module())
8✔
193
                .map(|(_, dep)| dep.create_module_define())
6✔
194
                .collect::<im::Vector<_>>();
1✔
195
            module_env.insert("notify".into(), nested_env::EnvKey::List(all_modules));
1✔
196
        }
292✔
197

198
        /* merge the module's local env */
199
        module_env.merge(&self.env_local);
293✔
200

201
        (module_env, build_dep_modules)
293✔
202
    }
293✔
203

204
    fn create_module_define(&self) -> String {
381✔
205
        self.name
381✔
206
            .chars()
381✔
207
            .map(|x| match x {
5,868✔
208
                'a'..='z' => x.to_ascii_uppercase(),
5,188✔
209
                '/' => '_',
10✔
210
                '.' => '_',
×
211
                '-' => '_',
10✔
212
                ':' => '_',
280✔
213
                _ => x,
380✔
214
            })
5,868✔
215
            .collect()
381✔
216
    }
381✔
217

218
    pub fn apply_early_env(&mut self) -> Result<(), Error> {
118✔
219
        self.env_local.expand(&self.env_early)?;
118✔
220
        self.env_export.expand(&self.env_early)?;
118✔
221
        self.env_global.expand(&self.env_early)?;
118✔
222
        Ok(())
118✔
223
    }
118✔
224

225
    // adds a Ninja target name as build dependency for this target.
226
    // gets env expanded on module instantiation.
227
    pub fn add_build_dep_file(&mut self, dep: &Utf8Path) {
2✔
228
        if let Some(build_dep_files) = &mut self.build_dep_files {
2✔
229
            build_dep_files.insert(dep.to_owned());
×
230
        } else {
2✔
231
            self.build_dep_files = Some(indexset![dep.to_owned()]);
2✔
232
        }
2✔
233
    }
2✔
234

235
    pub fn is_context_module(&self) -> bool {
8✔
236
        self.name.starts_with("context::")
8✔
237
    }
8✔
238

239
    pub fn add_conflicts<I>(&mut self, more_conflicts: I)
5✔
240
    where
5✔
241
        I: IntoIterator,
5✔
242
        I::Item: AsRef<str>,
5✔
243
    {
244
        let conflicts = self.conflicts.get_or_insert_with(Vec::new);
5✔
245
        for dep_name in more_conflicts {
5✔
246
            conflicts.push(dep_name.as_ref().into());
5✔
247
        }
5✔
248
    }
5✔
249

250
    pub(crate) fn add_provides<I>(&mut self, provides_unique: I)
3✔
251
    where
3✔
252
        I: IntoIterator,
3✔
253
        I::Item: AsRef<str>,
3✔
254
    {
255
        let provides = self.provides.get_or_insert_with(Vec::new);
3✔
256
        for module in provides_unique {
3✔
257
            provides.push(module.as_ref().into());
3✔
258
        }
3✔
259
    }
3✔
260

NEW
261
    pub(crate) fn add_requires<I>(&mut self, new_requires: I)
×
NEW
262
    where
×
NEW
263
        I: IntoIterator,
×
NEW
264
        I::Item: AsRef<str>,
×
265
    {
NEW
266
        let requires = self.requires.get_or_insert_with(Vec::new);
×
NEW
267
        for module in new_requires {
×
NEW
268
            requires.push(module.as_ref().into());
×
NEW
269
        }
×
NEW
270
    }
×
271
    // returns all fixed and optional sources with srcdir prepended
272
    // pub fn get_all_sources(&self, srcdir: Utf8PathBuf) -> Vec<Utf8PathBuf> {
273
    //     let mut res = self
274
    //         .sources
275
    //         .iter()
276
    //         .map(|source| {
277
    //             let mut path = srcdir.clone();
278
    //             path.push(source);
279
    //             path
280
    //         })
281
    //         .collect::<Vec<_>>();
282

283
    //     if let Some(sources_optional) = &self.sources_optional {
284
    //         res.extend(sources_optional.values().flatten().map(|x| {
285
    //             let mut path = srcdir.clone();
286
    //             path.push(x);
287
    //             path
288
    //         }));
289
    //     }
290

291
    //     res
292
    // }
293
}
294

295
impl Hash for Module {
296
    fn hash<H: Hasher>(&self, state: &mut H) {
6✔
297
        self.name.hash(state);
6✔
298
        self.context_name.hash(state);
6✔
299
    }
6✔
300
}
301

302
impl PartialEq for Module {
303
    fn eq(&self, other: &Self) -> bool {
376✔
304
        self.name == other.name && self.context_name == other.context_name
376✔
305
    }
376✔
306
}
307

308
impl fmt::Display for Module {
309
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
×
310
        match &self.context_name[..] {
×
311
            "default" => write!(f, "{}", self.name),
×
312
            _ => write!(f, "{}:{}", self.context_name, self.name),
×
313
        }
314
    }
×
315
}
316

317
#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)]
318
pub struct CustomBuild {
319
    pub gcc_deps: Option<String>,
320
    pub cmd: Vec<String>,
321
    pub out: Option<Vec<String>>,
322
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc