rpkg_config/
lib.rs

1#![doc = include_str!("../README.md")]
2
3#[cfg(test)]
4mod tests;
5
6use std::{
7    collections::HashMap,
8    error::Error,
9    ffi::{OsStr, OsString},
10    fmt::{self, Display},
11    fs, io,
12    iter::FusedIterator,
13    path::{Path, PathBuf},
14    str::{self, FromStr},
15};
16
17const VARIABLE_RECURSION_LIMIT: usize = 1000;
18
19/// Error returned by `PkgConfig::open`
20#[derive(Debug)]
21pub enum OpenError {
22    Io(io::Error),
23    Parse(ParseError),
24    Encoding(EncodingError),
25}
26
27impl Display for OpenError {
28    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
29        match self {
30            Self::Io(e) => Display::fmt(&e, f),
31            Self::Parse(e) => Display::fmt(&e, f),
32            Self::Encoding(e) => Display::fmt(&e, f),
33        }
34    }
35}
36
37impl Error for OpenError {}
38
39impl From<io::Error> for OpenError {
40    fn from(value: io::Error) -> Self {
41        Self::Io(value)
42    }
43}
44
45impl From<ParseError> for OpenError {
46    fn from(value: ParseError) -> Self {
47        Self::Parse(value)
48    }
49}
50
51impl From<EncodingError> for OpenError {
52    fn from(value: EncodingError) -> Self {
53        Self::Encoding(value)
54    }
55}
56
57/// Error returned if there's a problem with the text encoding
58#[derive(Clone, Copy, Debug)]
59pub struct EncodingError;
60
61impl Display for EncodingError {
62    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
63        write!(f, "data must be valid utf-8 on this platform")
64    }
65}
66
67impl Error for EncodingError {}
68
69/// Error returned when something couldn't be parsed
70#[derive(Clone, Copy, Debug)]
71pub struct ParseError {
72    line: usize,
73    description: &'static str,
74}
75
76impl ParseError {
77    pub fn new(line: usize, description: &'static str) -> Self {
78        Self { line, description }
79    }
80}
81
82impl Display for ParseError {
83    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
84        write!(f, "parse error at line {}: {}", self.line, self.description)
85    }
86}
87
88impl Error for ParseError {}
89
90/// Error returned when a variable couldn't be resolved
91#[derive(Debug)]
92pub enum VariableError {
93    Undefined(Vec<u8>),
94    RecursionLimit,
95}
96
97impl Display for VariableError {
98    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
99        match self {
100            Self::Undefined(var) => Display::fmt(&VariableErrorRef::Undefined(var), f),
101            Self::RecursionLimit => Display::fmt(&VariableErrorRef::RecursionLimit, f),
102        }
103    }
104}
105
106impl Error for VariableError {}
107
108impl From<VariableErrorRef<'_>> for VariableError {
109    fn from(value: VariableErrorRef) -> Self {
110        match value {
111            VariableErrorRef::Undefined(var) => VariableError::Undefined(var.to_owned()),
112            VariableErrorRef::RecursionLimit => VariableError::RecursionLimit,
113        }
114    }
115}
116
117#[derive(Debug)]
118enum VariableErrorRef<'a> {
119    Undefined(&'a [u8]),
120    RecursionLimit,
121}
122
123impl Display for VariableErrorRef<'_> {
124    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
125        match self {
126            Self::Undefined(var) => {
127                write!(f, "undefined variable: `{}`", String::from_utf8_lossy(var))
128            }
129            Self::RecursionLimit => write!(f, "recursion limit reached"),
130        }
131    }
132}
133
134impl Error for VariableErrorRef<'_> {}
135
136fn split_bytes_once(bytes: &[u8], m: impl Fn(u8) -> bool) -> Option<(&[u8], &[u8])> {
137    bytes
138        .iter()
139        .copied()
140        .position(m)
141        .map(|i| (&bytes[..i], &bytes[i + 1..]))
142}
143
144fn trim_bytes(bytes: &[u8]) -> &[u8] {
145    let Some(s) = bytes.iter().position(|b| !b.is_ascii_whitespace()) else {
146        return &bytes[bytes.len()..];
147    };
148    let e = bytes
149        .iter()
150        .rev()
151        .position(|b| !b.is_ascii_whitespace())
152        .unwrap();
153    &bytes[s..bytes.len() - e]
154}
155
156fn bytes_to_pathbuf(bytes: &[u8]) -> Result<PathBuf, EncodingError> {
157    byte_vec_to_pathbuf(bytes.to_owned())
158}
159
160fn byte_vec_to_pathbuf(bytes: Vec<u8>) -> Result<PathBuf, EncodingError> {
161    Ok(PathBuf::from(OsString::from_byte_vec(bytes)?))
162}
163
164fn escape_bytes(bytes: &[u8]) -> Vec<u8> {
165    let mut escaped = Vec::new();
166    for b in bytes.iter().copied() {
167        match b {
168            b'\\' | b'\"' | b' ' => {
169                escaped.push(b'\\');
170                escaped.push(b);
171            }
172            _ => escaped.push(b),
173        }
174    }
175    escaped
176}
177
178trait BytesConv {
179    fn as_bytes(&self) -> Result<&[u8], EncodingError>;
180    fn from_bytes(bytes: &[u8]) -> Result<&Self, EncodingError>;
181}
182
183trait ByteVecConv: Sized {
184    #[allow(dead_code)]
185    fn into_byte_vec(self) -> Result<Vec<u8>, EncodingError>;
186    fn from_byte_vec(bytes: Vec<u8>) -> Result<Self, EncodingError>;
187}
188
189#[cfg(unix)]
190impl BytesConv for OsStr {
191    fn as_bytes(&self) -> Result<&[u8], EncodingError> {
192        Ok(std::os::unix::ffi::OsStrExt::as_bytes(self))
193    }
194
195    fn from_bytes(bytes: &[u8]) -> Result<&Self, EncodingError> {
196        Ok(std::os::unix::ffi::OsStrExt::from_bytes(bytes))
197    }
198}
199
200#[cfg(unix)]
201impl ByteVecConv for OsString {
202    fn into_byte_vec(self) -> Result<Vec<u8>, EncodingError> {
203        Ok(std::os::unix::ffi::OsStringExt::into_vec(self))
204    }
205
206    fn from_byte_vec(bytes: Vec<u8>) -> Result<Self, EncodingError> {
207        Ok(std::os::unix::ffi::OsStringExt::from_vec(bytes))
208    }
209}
210
211#[cfg(not(unix))]
212impl BytesConv for OsStr {
213    fn as_bytes(&self) -> Result<&[u8], EncodingError> {
214        self.to_str().map(|s| s.as_bytes()).ok_or(EncodingError)
215    }
216
217    fn from_bytes(bytes: &[u8]) -> Result<&Self, EncodingError> {
218        Ok(OsStr::new(
219            str::from_utf8(bytes).map_err(|_| EncodingError)?,
220        ))
221    }
222}
223
224#[cfg(not(unix))]
225impl ByteVecConv for OsString {
226    fn into_byte_vec(self) -> Result<Vec<u8>, EncodingError> {
227        Ok(self.into_string().map_err(|_| EncodingError)?.into_bytes())
228    }
229
230    fn from_byte_vec(bytes: Vec<u8>) -> Result<Self, EncodingError> {
231        Ok(String::from_utf8(bytes).map_err(|_| EncodingError)?.into())
232    }
233}
234
235/// Items parsed from `Libs` or `Libs.private` keys
236#[non_exhaustive]
237#[derive(Clone, Debug)]
238pub enum Link {
239    /// Library search path
240    SearchLib(PathBuf),
241
242    /// Framework search path
243    SearchFramework(PathBuf),
244
245    /// Link a library (static or dynamic)
246    Lib(PathBuf),
247
248    /// Link a framework
249    Framework(PathBuf),
250
251    /// Link a weak framework
252    WeakFramework(PathBuf),
253
254    /// Set rpath
255    Rpath(PathBuf),
256
257    /// Anything else. In the future, items that currently use this variant
258    /// may get their own variants instead.
259    Verbatim(PathBuf),
260}
261
262/// A parsed pkg-config pc file
263#[derive(Clone, Default)]
264pub struct PkgConfig {
265    variables: HashMap<Vec<u8>, Vec<u8>>,
266    keys: HashMap<Vec<u8>, Vec<u8>>,
267}
268
269impl PkgConfig {
270    /// Open and parse a `pkg-config` `.pc` file. This defines the `pcfiledir` variable as the given path.
271    pub fn open(path: &Path) -> Result<Self, OpenError> {
272        let mut cfg = Self::default();
273        cfg.set_variable_escaped(
274            "pcfiledir",
275            path.parent().unwrap().as_os_str().as_bytes()?,
276            true,
277        );
278        cfg.extend_from_bytes(&fs::read(path)?)?;
279        Ok(cfg)
280    }
281
282    /// Read a `pkg-config` `.pc` file from a byte slice
283    pub fn from_bytes(bytes: &[u8]) -> Result<Self, ParseError> {
284        let mut cfg = Self::default();
285        cfg.extend_from_bytes(bytes)?;
286        Ok(cfg)
287    }
288
289    fn extend_from_bytes(&mut self, bytes: &[u8]) -> Result<(), ParseError> {
290        for (line_no, line) in PcLines::new(bytes) {
291            let line = trim_bytes(&line);
292            if line.is_empty() {
293                continue;
294            }
295
296            OsStr::from_bytes(line).map_err(|_| {
297                ParseError::new(line_no, "data must be valid utf-8 on this platform")
298            })?;
299
300            if let Some(i) = line.iter().position(|b| matches!(b, b':' | b'=')) {
301                if line[i] == b'=' {
302                    // variable
303                    let (key, value) = split_bytes_once(line, |b| b == b'=').unwrap();
304                    let key = trim_bytes(key);
305                    if key.iter().any(u8::is_ascii_whitespace) {
306                        return Err(ParseError::new(line_no, "space in variable name"));
307                    }
308                    if !self.set_variable(key, value, false) {
309                        return Err(ParseError::new(line_no, "duplicate variable definition"));
310                    }
311                } else {
312                    // key
313                    let (key, value) = split_bytes_once(line, |b| b == b':').unwrap();
314                    let mut key = trim_bytes(key).to_owned();
315                    if key == b"CFlags" {
316                        // map CFlags to Cflags
317                        key[1] = b'f';
318                    }
319                    let value = trim_bytes(value).to_owned();
320                    if self.keys.insert(key, value).is_some() {
321                        return Err(ParseError::new(line_no, "key already set"));
322                    }
323                }
324            } else {
325                return Err(ParseError::new(line_no, "not a variable or key definition"));
326            }
327        }
328        Ok(())
329    }
330
331    /// Get an iterator over the items in the `Libs` key
332    pub fn libs(&self) -> Result<Links, VariableError> {
333        Ok(Links {
334            split: self
335                .key_bytes_expanded_unescaped_split("Libs")?
336                .unwrap_or(UnescapeAndSplit::new(b"")),
337        })
338    }
339
340    /// Get an iterator over the items in the `Libs.private` key
341    pub fn libs_private(&self) -> Result<Links, VariableError> {
342        Ok(Links {
343            split: self
344                .key_bytes_expanded_unescaped_split("Libs.private")?
345                .unwrap_or(UnescapeAndSplit::new(b"")),
346        })
347    }
348
349    pub fn libs_with_private(
350        &self,
351        include_private: bool,
352    ) -> Result<impl FusedIterator<Item = Link>, VariableError> {
353        Ok(self.libs()?.chain(Links {
354            split: if include_private {
355                self.key_bytes_expanded_unescaped_split("Libs.private")?
356                    .unwrap_or(UnescapeAndSplit::new(b""))
357            } else {
358                UnescapeAndSplit::new(b"")
359            },
360        }))
361    }
362
363    /// Get a variable
364    pub fn variable(&self, var: impl AsRef<[u8]>) -> Option<&[u8]> {
365        self.variables.get(var.as_ref()).map(|v| v as &[u8])
366    }
367
368    /// Set a variable
369    pub fn set_variable(
370        &mut self,
371        var: impl AsRef<[u8]>,
372        value: impl AsRef<[u8]>,
373        allow_overwrite: bool,
374    ) -> bool {
375        self.variables
376            .insert(
377                trim_bytes(var.as_ref()).to_owned(),
378                trim_bytes(value.as_ref()).to_owned(),
379            )
380            .is_none()
381            || allow_overwrite
382    }
383
384    /// Set a variable and escape it
385    pub fn set_variable_escaped(
386        &mut self,
387        var: impl AsRef<[u8]>,
388        value: impl AsRef<[u8]>,
389        allow_overwrite: bool,
390    ) -> bool {
391        self.set_variable(var, escape_bytes(value.as_ref()), allow_overwrite)
392    }
393
394    /// Get a variable and expand all variables it references
395    pub fn expand_variable(&self, var: impl AsRef<[u8]>) -> Result<Vec<u8>, VariableError> {
396        Ok(self.expand_variable_r(var.as_ref(), VARIABLE_RECURSION_LIMIT)?)
397    }
398
399    fn expand_variable_r<'e, 's: 'e, 'k: 'e>(
400        &'s self,
401        var: &'k [u8],
402        limit: usize,
403    ) -> Result<Vec<u8>, VariableErrorRef<'e>> {
404        self.expand_variables_in_byte_string_r(
405            self.variable(var).ok_or(VariableErrorRef::Undefined(var))?,
406            limit,
407        )
408    }
409
410    /// Expand all variables in the input
411    pub fn expand_variables_in(&self, input: impl AsRef<[u8]>) -> Result<Vec<u8>, VariableError> {
412        Ok(self.expand_variables_in_byte_string_r(input.as_ref(), VARIABLE_RECURSION_LIMIT)?)
413    }
414
415    fn expand_variables_in_byte_string_r<'e, 's: 'e, 'k: 'e>(
416        &'s self,
417        input: &'k [u8],
418        limit: usize,
419    ) -> Result<Vec<u8>, VariableErrorRef<'e>> {
420        let mut output = Vec::new();
421        let mut bytes = input.iter().copied().enumerate();
422        // - '$' followed by '$' outputs '$'
423        // - '$' followed by '{' variable-name '}' expands variable
424        // - anything else is copied verbatim
425        while let Some((_, b)) = bytes.next() {
426            if b == b'$' {
427                if let Some((ni, nb)) = bytes.next() {
428                    match nb {
429                        b'$' => output.push(b),
430                        b'{' => {
431                            if limit == 0 {
432                                return Err(VariableErrorRef::RecursionLimit);
433                            }
434                            let e = 'end_pos: {
435                                for (nni, nnb) in bytes.by_ref() {
436                                    if nnb == b'}' {
437                                        break 'end_pos nni;
438                                    }
439                                }
440                                // unterminated variable is still expanded
441                                input.len()
442                            };
443                            output.extend(self.expand_variable_r(&input[ni + 1..e], limit - 1)?);
444                        }
445                        _ => {
446                            output.push(b);
447                            output.push(nb);
448                        }
449                    }
450                }
451            } else {
452                output.push(b);
453            }
454        }
455        Ok(output)
456    }
457
458    /// Get the raw bytes of a key
459    pub fn key_bytes(&self, key: impl AsRef<[u8]>) -> Option<&[u8]> {
460        self.keys.get(key.as_ref()).map(|v| v as &[u8])
461    }
462
463    /// Get the raw bytes of a key with variables expanded
464    pub fn key_bytes_expanded(
465        &self,
466        key: impl AsRef<[u8]>,
467    ) -> Result<Option<Vec<u8>>, VariableError> {
468        if let Some(bytes) = self.key_bytes(key) {
469            Ok(Some(self.expand_variables_in(bytes)?))
470        } else {
471            Ok(None)
472        }
473    }
474
475    /// Get the raw bytes of a key with variables expanded, unescaped and split by spaces
476    pub fn key_bytes_expanded_unescaped_split(
477        &self,
478        key: impl AsRef<[u8]>,
479    ) -> Result<Option<UnescapeAndSplit>, VariableError> {
480        Ok(self.key_bytes_expanded(key)?.map(UnescapeAndSplit::new))
481    }
482}
483
484impl FromStr for PkgConfig {
485    type Err = ParseError;
486
487    fn from_str(s: &str) -> Result<Self, Self::Err> {
488        Self::from_bytes(s.as_bytes())
489    }
490}
491
492/// Iterator over a pkg-config key that handles escapes and splits by unescaped whitespace
493pub struct UnescapeAndSplit {
494    bytes: Vec<u8>,
495    index: usize,
496}
497
498impl UnescapeAndSplit {
499    fn new(bytes: impl AsRef<[u8]>) -> Self {
500        Self {
501            bytes: trim_bytes(bytes.as_ref()).to_owned(),
502            index: 0,
503        }
504    }
505}
506
507impl FusedIterator for UnescapeAndSplit {}
508
509impl Iterator for UnescapeAndSplit {
510    type Item = Vec<u8>;
511
512    fn next(&mut self) -> Option<Self::Item> {
513        let i = self.bytes[self.index..]
514            .iter()
515            .position(|b| !b.is_ascii_whitespace())
516            .unwrap_or(self.bytes.len() - self.index);
517        self.index += i;
518
519        if self.bytes.len() == self.index {
520            return None;
521        }
522
523        let mut it = self.bytes[self.index..].iter().copied().enumerate();
524        let mut quoted = false;
525        let mut item = Vec::new();
526
527        while let Some((i, b)) = it.next() {
528            match b {
529                b'\\' => {
530                    if let Some((_, b2)) = it.next() {
531                        match b2 {
532                            b'\\' | b'\"' | b' ' => {
533                                // escaped
534                                item.push(b2)
535                            }
536                            _ => {
537                                // may be a windows path
538                                item.push(b);
539                                item.push(b2);
540                            }
541                        }
542                    } else {
543                        // lone escape at end of input
544                        item.push(b);
545                    }
546                }
547
548                b'\"' => {
549                    quoted = !quoted;
550                }
551
552                _ => {
553                    if !quoted && b.is_ascii_whitespace() {
554                        self.index += i;
555                        return Some(item);
556                    } else {
557                        item.push(b);
558                    }
559                }
560            }
561        }
562
563        self.index = self.bytes.len();
564        Some(item)
565    }
566
567    fn size_hint(&self) -> (usize, Option<usize>) {
568        (0, Some((self.bytes.len() + 1) / 2))
569    }
570}
571
572/// Iterator over items in `Libs` or `Libs.private`
573pub struct Links {
574    split: UnescapeAndSplit,
575}
576
577impl FusedIterator for Links {}
578
579impl Iterator for Links {
580    type Item = Link;
581
582    fn next(&mut self) -> Option<Self::Item> {
583        if let Some(part) = self.split.next() {
584            if let Some(part) = part.strip_prefix(b"-L") {
585                Some(Link::SearchLib(bytes_to_pathbuf(part).unwrap()))
586            } else if let Some(part) = part.strip_prefix(b"-F") {
587                Some(Link::SearchFramework(bytes_to_pathbuf(part).unwrap()))
588            } else if let Some(part) = part.strip_prefix(b"-l") {
589                Some(Link::Lib(bytes_to_pathbuf(part).unwrap()))
590            } else if let Some(part) = part.strip_prefix(b"-Wl,-framework,") {
591                Some(Link::Framework(bytes_to_pathbuf(part).unwrap()))
592            } else if part == b"-framework" {
593                self.split
594                    .next()
595                    .map(|part| Link::Framework(byte_vec_to_pathbuf(part).unwrap()))
596            } else if let Some(part) = part.strip_prefix(b"-Wl,-weak_framework,") {
597                Some(Link::WeakFramework(bytes_to_pathbuf(part).unwrap()))
598            } else if part == b"-weak_framework" {
599                self.split
600                    .next()
601                    .map(|part| Link::WeakFramework(byte_vec_to_pathbuf(part).unwrap()))
602            } else if let Some(part) = part.strip_prefix(b"-Wl,-rpath,") {
603                Some(Link::Rpath(bytes_to_pathbuf(part).unwrap()))
604            } else if part == b"-rpath" {
605                self.split
606                    .next()
607                    .map(|part| Link::Rpath(byte_vec_to_pathbuf(part).unwrap()))
608            } else {
609                Some(Link::Verbatim(byte_vec_to_pathbuf(part).unwrap()))
610            }
611        } else {
612            None
613        }
614    }
615}
616
617struct PcLines<'a> {
618    bytes: &'a [u8],
619    line_no: usize,
620}
621
622impl<'a> PcLines<'a> {
623    fn new(bytes: &'a [u8]) -> Self {
624        Self { bytes, line_no: 0 }
625    }
626}
627
628impl Iterator for PcLines<'_> {
629    type Item = (usize, Vec<u8>);
630
631    fn next(&mut self) -> Option<Self::Item> {
632        // .pc parsing rules:
633        // - newline is one of '\n', '\r', '\r\n' or '\n\r'
634        // - '#' starts a comment that goes until the next newline
635        // - '\' escapes '#' or newline only. newline in comments can't be escaped
636        if self.bytes.is_empty() {
637            return None;
638        }
639        let mut it = self.bytes.iter().copied().enumerate().peekable();
640        let mut line = Vec::new();
641        self.line_no += 1;
642        let line_no = self.line_no;
643        while let Some((i, b)) = it.next() {
644            match b {
645                b'\\' => {
646                    // escapes
647                    if let Some((_, nb)) = it.next() {
648                        match nb {
649                            b'#' => line.push(nb),
650                            b'\r' | b'\n' => {
651                                if let Some((_, nnb)) = it.peek() {
652                                    if *nnb == if nb == b'\r' { b'\n' } else { b'\r' } {
653                                        it.next();
654                                    }
655                                }
656                                self.line_no += 1;
657                            }
658                            _ => {
659                                line.push(b);
660                                line.push(nb);
661                            }
662                        }
663                    } else {
664                        line.push(b);
665                        self.bytes = &self.bytes[i + 1..];
666                        return Some((line_no, line));
667                    }
668                }
669                b'#' => {
670                    // comment
671                    while let Some((ni, nc)) = it.next() {
672                        if matches!(nc, b'\r' | b'\n') {
673                            self.bytes = &self.bytes[ni + 1..];
674                            if let Some((_, nnc)) = it.peek() {
675                                if *nnc == if nc == b'\r' { b'\n' } else { b'\r' } {
676                                    self.bytes = &self.bytes[1..];
677                                }
678                            }
679                            break;
680                        }
681                    }
682                    return Some((line_no, line));
683                }
684                b'\r' | b'\n' => {
685                    // newline
686                    if let Some((_, nc)) = it.next() {
687                        if nc == if b == b'\r' { b'\n' } else { b'\r' } {
688                            self.bytes = &self.bytes[i + 2..];
689                        } else {
690                            self.bytes = &self.bytes[i + 1..];
691                        }
692                        return Some((line_no, line));
693                    } else {
694                        self.bytes = &self.bytes[i + 1..];
695                        return Some((line_no, line));
696                    }
697                }
698                _ => line.push(b),
699            }
700        }
701        Some((line_no, line))
702    }
703}