provenant/parsers/
conan_data.rs1use crate::models::{DatasourceId, PackageType, Sha256Digest};
22use std::collections::HashMap;
23use std::path::Path;
24
25use crate::parser_warn as warn;
26use serde::{Deserialize, Serialize};
27use serde_json::json;
28
29use crate::models::PackageData;
30use crate::parsers::utils::{MAX_ITERATION_COUNT, read_file_to_string, truncate_field};
31
32use super::PackageParser;
33
34const PACKAGE_TYPE: PackageType = PackageType::Conan;
35
36fn default_package_data() -> PackageData {
37 PackageData {
38 package_type: Some(PACKAGE_TYPE),
39 primary_language: Some("C++".to_string()),
40 datasource_id: Some(DatasourceId::ConanConanDataYml),
41 ..Default::default()
42 }
43}
44
45pub struct ConanDataParser;
47
48#[derive(Debug, Deserialize, Serialize)]
49struct ConanDataYml {
50 sources: Option<HashMap<String, SourceInfo>>,
51 patches: Option<HashMap<String, PatchesValue>>,
52}
53
54#[derive(Debug, Deserialize, Serialize)]
55#[serde(untagged)]
56enum UrlValue {
57 Single(String),
58 Multiple(Vec<String>),
59}
60
61#[derive(Debug, Deserialize, Serialize)]
62#[serde(untagged)]
63enum PatchesValue {
64 List(Vec<PatchInfo>),
65 String(String),
66}
67
68#[derive(Debug, Deserialize, Serialize)]
69struct PatchInfo {
70 patch_file: Option<String>,
71 patch_description: Option<String>,
72 patch_type: Option<String>,
73}
74
75#[derive(Debug, Deserialize, Serialize)]
76struct SourceInfo {
77 url: Option<UrlValue>,
78 sha256: Option<String>,
79}
80
81impl PackageParser for ConanDataParser {
82 const PACKAGE_TYPE: PackageType = PACKAGE_TYPE;
83
84 fn is_match(path: &Path) -> bool {
85 path.to_str().is_some_and(|p| p.ends_with("/conandata.yml"))
86 }
87
88 fn extract_packages(path: &Path) -> Vec<PackageData> {
89 let content = match read_file_to_string(path, None) {
90 Ok(c) => c,
91 Err(e) => {
92 warn!("Failed to read conandata.yml file {:?}: {}", path, e);
93 return vec![default_package_data()];
94 }
95 };
96
97 parse_conandata_yml(&content)
98 }
99}
100
101pub(crate) fn parse_conandata_yml(content: &str) -> Vec<PackageData> {
102 let data: ConanDataYml = match yaml_serde::from_str(content) {
103 Ok(d) => d,
104 Err(e) => {
105 warn!("Failed to parse conandata.yml: {}", e);
106 return vec![default_package_data()];
107 }
108 };
109
110 let Some(sources) = data.sources else {
111 return vec![default_package_data()];
112 };
113
114 let mut packages = Vec::new();
115
116 for (version, source_info) in sources.into_iter().take(MAX_ITERATION_COUNT) {
117 let mut extra_data = HashMap::new();
118
119 let download_url = match &source_info.url {
120 Some(UrlValue::Single(url)) => Some(truncate_field(url.clone())),
121 Some(UrlValue::Multiple(urls)) if !urls.is_empty() => {
122 Some(truncate_field(urls[0].clone()))
123 }
124 _ => None,
125 };
126
127 if let Some(UrlValue::Multiple(urls)) = &source_info.url
128 && urls.len() > 1
129 {
130 extra_data.insert("mirror_urls".to_string(), json!(urls));
131 }
132
133 if let Some(ref patches_map) = data.patches
134 && let Some(patches_value) = patches_map.get(&version)
135 {
136 let patches_json = match patches_value {
137 PatchesValue::List(patches) => {
138 let patches_data: Vec<_> = patches
139 .iter()
140 .map(|p| {
141 json!({
142 "patch_file": p.patch_file,
143 "patch_description": p.patch_description,
144 "patch_type": p.patch_type,
145 })
146 })
147 .collect();
148 json!(patches_data)
149 }
150 PatchesValue::String(s) => json!(s),
151 };
152 extra_data.insert("patches".to_string(), patches_json);
153 }
154
155 packages.push(PackageData {
156 package_type: Some(PACKAGE_TYPE),
157 primary_language: Some("C++".to_string()),
158 version: Some(truncate_field(version)),
159 download_url,
160 sha256: source_info
161 .sha256
162 .and_then(|h| Sha256Digest::from_hex(&h).ok()),
163 extra_data: if extra_data.is_empty() {
164 None
165 } else {
166 Some(extra_data)
167 },
168 datasource_id: Some(DatasourceId::ConanConanDataYml),
169 ..Default::default()
170 });
171 }
172
173 if packages.is_empty() {
174 packages.push(default_package_data());
175 }
176
177 packages
178}
179
180crate::register_parser!(
181 "Conan external source metadata",
182 &["*/conandata.yml"],
183 "conan",
184 "C++",
185 Some("https://docs.conan.io/2/tutorial/creating_packages/handle_sources_in_packages.html"),
186);