Skip to main content

debian_control/lossy/
buildinfo.rs

1//! Parser for Debian buildinfo files
2//!
3//! The buildinfo file format is a Debian-specific format that is used to store
4//! information about the build environment of a package. See <https://wiki.debian.org/Buildinfo> for
5//! more information.
6use crate::lossy::relations::Relations;
7use deb822_fast::FromDeb822Paragraph;
8use deb822_fast::{FromDeb822, ToDeb822};
9use std::collections::HashMap;
10use std::path::PathBuf;
11
12fn deserialize_env(s: &str) -> Result<HashMap<String, String>, String> {
13    let mut env = HashMap::new();
14    for line in s.lines() {
15        if line.trim().is_empty() {
16            continue;
17        }
18        let (key, value) = match line.split_once("=") {
19            Some((key, value)) => {
20                // Strip surrounding double quotes, but only when there are two
21                // of them: a lone `"` has len()==1 and would panic on 1..0.
22                if value.len() >= 2 && value.starts_with('"') && value.ends_with('"') {
23                    let value = value[1..value.len() - 1].to_string();
24                    (key, value)
25                } else {
26                    (key, value.to_string())
27                }
28            }
29            None => {
30                // If there is no '=', then the line is invalid
31                return Err("Invalid environment variable".to_string());
32            }
33        };
34        env.insert(key.to_string(), value.to_string());
35    }
36    Ok(env)
37}
38
39fn serialize_env(env: &HashMap<String, String>) -> String {
40    let mut s = String::new();
41    for (key, value) in env {
42        s.push_str(&format!("{}={}\n", key, value));
43    }
44    s
45}
46
47fn deserialize_version(s: &str) -> Result<debversion::Version, String> {
48    s.parse().map_err(|e: debversion::ParseError| e.to_string())
49}
50
51fn serialize_version(version: &debversion::Version) -> String {
52    version.to_string()
53}
54
55fn serialize_pathbuf(path: &std::path::Path) -> String {
56    path.display().to_string()
57}
58
59fn deserialize_pathbuf(s: &str) -> Result<PathBuf, String> {
60    Ok(PathBuf::from(s))
61}
62
63#[derive(FromDeb822, ToDeb822)]
64/// The buildinfo file.
65pub struct Buildinfo {
66    #[deb822(field = "Format")]
67    /// The format of the buildinfo file.
68    format: String,
69
70    #[deb822(field = "Build-Architecture")]
71    /// The architecture the package is built on.
72    build_architecture: String,
73
74    #[deb822(field = "Source")]
75    /// The name of the source package.
76    source: String,
77
78    #[deb822(field = "Binary")]
79    /// Folded list of binary packages built from the source package.
80    binary: Option<String>,
81
82    #[deb822(field = "Architecture")]
83    /// The architecture the package is built for.
84    architecture: String,
85
86    #[deb822(
87        field = "Version",
88        deserialize_with = deserialize_version,
89        serialize_with = serialize_version
90    )]
91    /// The version number of a package.
92    version: debversion::Version,
93
94    #[deb822(field = "Binary-Only-Changes")]
95    binary_only_changes: Option<String>,
96
97    #[deb822(field = "Checksums-Sha256")]
98    /// The SHA256 checksums of the files in the package.
99    // TODO: Parse properly
100    checksums_sha256: Option<String>,
101
102    #[deb822(field = "Checksums-Sha1")]
103    /// The SHA1 checksums of the files in the package.
104    // TODO: Parse properly
105    checksums_sha1: Option<String>,
106
107    #[deb822(field = "Checksums-Md5")]
108    /// The MD5 checksums of the files in the package.
109    // TODO: Parse properly
110    checksums_md5: Option<String>,
111
112    #[deb822(field = "Build-Origin")]
113    /// The origin of the build.
114    build_origin: Option<String>,
115
116    #[deb822(field = "Build-Date")]
117    /// The date the package was built.
118    build_date: Option<String>,
119
120    #[deb822(field = "Build-Tainted-By")]
121    /// The reason the build is tainted.
122    build_tainted_by: Option<String>,
123
124    #[deb822(field = "Build-Path", deserialize_with = deserialize_pathbuf, serialize_with = serialize_pathbuf)]
125    /// Absolute path of the directory in which the package was built.
126    build_path: Option<PathBuf>,
127
128    #[deb822(
129        field = "Environment",
130        deserialize_with = deserialize_env,
131        serialize_with = serialize_env
132    )]
133    /// Environment variables used during the build.
134    environment: Option<HashMap<String, String>>,
135
136    #[deb822(field = "Installed-Build-Depends")]
137    /// The packages that this package depends on during build.
138    installed_build_depends: Option<Relations>,
139}
140
141impl std::str::FromStr for Buildinfo {
142    type Err = String;
143
144    fn from_str(s: &str) -> Result<Self, Self::Err> {
145        let para: deb822_fast::Paragraph =
146            s.parse().map_err(|e: deb822_fast::Error| e.to_string())?;
147        Self::from_paragraph(&para)
148    }
149}
150
151#[cfg(test)]
152mod tests {
153    use super::*;
154    use std::str::FromStr;
155
156    #[test]
157    fn test_buildinfo() {
158        let input = include_str!("../../testdata/ruff.buildinfo");
159
160        let buildinfo = Buildinfo::from_str(input).unwrap();
161
162        assert_eq!(buildinfo.format, "1.0");
163    }
164
165    #[test]
166    fn test_environment_value_is_lone_quote() {
167        // Regression: a value of just `"` used to panic in deserialize_env
168        // because it tried to slice 1..0 after detecting "starts/ends with
169        // double quote" on a one-byte string.
170        let line = "FOO=\"";
171        let env = super::deserialize_env(line).unwrap();
172        assert_eq!(env.get("FOO"), Some(&"\"".to_string()));
173    }
174}