Skip to main content

nom_kconfig/entry/source/
mod.rs

1#[cfg(feature = "kconfiglib")]
2mod orsource;
3#[cfg(feature = "kconfiglib")]
4mod osource;
5#[cfg(feature = "kconfiglib")]
6mod rsource;
7#[allow(clippy::module_inception)]
8mod source;
9
10use nom::{
11    branch::alt,
12    character::complete::{alphanumeric1, one_of},
13    combinator::{cut, map, recognize},
14    error::{Error, ErrorKind, ParseError},
15    multi::many1,
16    IResult, Parser,
17};
18
19#[cfg(feature = "kconfiglib")]
20pub use self::{
21    orsource::parse_orsource, orsource::OrSource, osource::parse_osource, osource::OSource,
22    rsource::parse_rsource, rsource::RSource,
23};
24pub use source::{parse_source, Source};
25#[cfg(feature = "debug")]
26use tracing::{debug, error};
27
28use crate::{kconfig::private_parse_kconfig, KconfigInput};
29use crate::{util::ws, Kconfig, KconfigFile};
30
31#[cfg(feature = "glob-wildcard")]
32pub use glob::glob;
33use std::collections::HashMap;
34#[cfg(feature = "glob-wildcard")]
35use std::path::PathBuf;
36
37#[cfg(test)]
38mod source_test;
39
40#[cfg(feature = "glob-wildcard")]
41enum JoinPathMode {
42    Relative,
43
44    Root,
45}
46
47pub(crate) fn parse_filepath(input: KconfigInput<'_>) -> IResult<KconfigInput<'_>, &str> {
48    map(
49        recognize(ws(many1(alt((
50            alphanumeric1::<KconfigInput, _>,
51            recognize(one_of(".$(){}*-_$+@/")),
52        ))))),
53        |d| d.fragment().to_owned(),
54    )
55    .parse(input)
56}
57
58#[allow(clippy::type_complexity)]
59fn parse_source_kconfig(
60    input: KconfigInput,
61    source_kconfig_file: KconfigFile,
62) -> Result<(HashMap<String, String>, Kconfig), nom::Err<Error<KconfigInput>>> {
63    let source_content = source_kconfig_file
64        .read_to_string()
65        .map_err(|_| nom::Err::Error(Error::from_error_kind(input.clone(), ErrorKind::Fail)));
66
67    #[cfg(feature = "kconfiglib")]
68    {
69        // TODO
70        // if the file doesn't exist, it's probably because the filename is dynamically generated with macros/variables.
71        // In that case, we can return an empty Kconfig instead of failing to parse the source file.
72        //
73        // This is not the best solution !
74        if source_content.is_err() {
75            #[cfg(feature = "debug")]
76            error!(
77                "I tried to parse the source file '{}' defined in '{}'. This is likely because the filename is dynamically generated with macros/variables that are not supported yet. Returning an empty Kconfig for this source file.",
78                source_kconfig_file.full_path().display(),
79                input.extra.full_path().display()
80            );
81            return Ok((
82                input.extra.vars(),
83                Kconfig {
84                    file: source_kconfig_file.full_path().display().to_string(),
85                    entries: vec![],
86                },
87            ));
88        }
89    }
90
91    let source_content = source_content?;
92
93    #[allow(clippy::let_and_return)]
94    let x = match cut(private_parse_kconfig).parse(KconfigInput::new_extra(
95        &source_content,
96        source_kconfig_file.clone(),
97    )) {
98        Ok((d, kconfig)) => Ok(((*d.extra.local_vars).clone(), kconfig)),
99        Err(e) => {
100            //debug!("Variables are {:?}", input.extra.vars());
101            //error!(
102            //    "Failed to parse source file '{}'",
103            //    source_kconfig_file.full_path().display(),
104            //);
105            match e {
106                nom::Err::Incomplete(needed) => {
107                    return Err(nom::Err::Incomplete(needed));
108                }
109                nom::Err::Error(e) => {
110                    return Err(nom::Err::Error(Error::new(
111                        unsafe {
112                            KconfigInput::new_from_raw_offset(
113                                e.input.location_offset(),
114                                e.input.location_line(),
115                                String::leak(e.input.fragment().to_string()),
116                                e.input.extra,
117                            )
118                        },
119                        e.code,
120                    )))
121                }
122                nom::Err::Failure(e) => {
123                    return Err(nom::Err::Failure(Error::new(
124                        unsafe {
125                            KconfigInput::new_from_raw_offset(
126                                e.input.location_offset(),
127                                e.input.location_line(),
128                                String::leak(e.input.fragment().to_string()),
129                                e.input.extra,
130                            )
131                        },
132                        e.code,
133                    )));
134                }
135            }
136        }
137    };
138    x
139}
140
141#[cfg(feature = "glob-wildcard")]
142fn expand_source_files<'a>(
143    input: KconfigInput<'a>,
144    file: &str,
145    mode: JoinPathMode,
146) -> Result<Vec<PathBuf>, nom::Err<Error<KconfigInput<'a>>>> {
147    let mut expanded_files = Vec::new();
148    let prefix_path = match mode {
149        JoinPathMode::Relative => input.extra.full_path().parent().unwrap().to_path_buf(),
150        JoinPathMode::Root => input.extra.root_dir.clone(),
151    };
152
153    let full_path_pattern = prefix_path.join(file);
154    let paths: Vec<PathBuf> = glob(&full_path_pattern.display().to_string())
155        .map_err(|_| nom::Err::Error(Error::from_error_kind(input.clone(), ErrorKind::Fail)))?
156        .collect::<Result<Vec<_>, _>>()
157        .map_err(|_| nom::Err::Error(Error::from_error_kind(input.clone(), ErrorKind::Fail)))?;
158
159    if paths.is_empty() {
160        return Ok(vec![prefix_path.join(file)]);
161    }
162    for source_path in paths {
163        let source_path = source_path.canonicalize().unwrap();
164        let source_path_without_root = source_path
165            // TODO need to canonicalize because of macOS weird filepath for temp directories
166            // /var/folder
167            // and /private/var/folder
168            .strip_prefix(input.extra.root_dir.canonicalize().unwrap())
169            .map_err(|_| nom::Err::Error(Error::from_error_kind(input.clone(), ErrorKind::Fail)))?;
170        expanded_files.push(source_path_without_root.to_path_buf());
171    }
172
173    expanded_files.sort();
174    if expanded_files.is_empty() {
175        #[cfg(feature = "debug")]
176        debug!("No expanded files found");
177        return Err(nom::Err::Error(Error::from_error_kind(
178            input,
179            ErrorKind::Fail,
180        )));
181    }
182
183    Ok(expanded_files)
184}
185
186#[cfg(test)]
187use crate::assert_parsing_eq;
188
189#[test]
190fn test_parse_filepath() {
191    assert_parsing_eq!(
192        parse_filepath,
193        "u-boot/board/sagem/f@st1704/Kconfig",
194        Ok(("", "u-boot/board/sagem/f@st1704/Kconfig"))
195    );
196
197    assert_parsing_eq!(
198        parse_filepath,
199        "u-boot/board/l+g/vinco/Kconfig",
200        Ok(("", "u-boot/board/l+g/vinco/Kconfig"))
201    );
202}