pkgcraft/pkg/ebuild/
raw.rs1use std::sync::{Arc, OnceLock};
2use std::time::Duration;
3use std::{fmt, fs};
4
5use camino::Utf8PathBuf;
6use indexmap::IndexMap;
7
8use crate::bash;
9use crate::dep::{Cpv, Dep};
10use crate::eapi::{self, Eapi};
11use crate::error::Error;
12use crate::macros::bool_not_equal;
13use crate::pkg::{Package, RepoPackage, make_pkg_traits};
14use crate::repo::ebuild::cache::{Cache, CacheEntry};
15use crate::repo::{EbuildRepo, Repository};
16use crate::traits::{FilterLines, Intersects};
17
18use super::metadata::Metadata;
19
20#[derive(Clone)]
21struct InternalEbuildRawPkg {
22 pub(super) cpv: Cpv,
23 pub(super) repo: EbuildRepo,
24 pub(super) eapi: &'static Eapi,
25 data: Arc<str>,
26 chksum: String,
27 tree: OnceLock<bash::Tree>,
28}
29
30#[derive(Clone)]
31pub struct EbuildRawPkg(Arc<InternalEbuildRawPkg>);
32
33make_pkg_traits!(EbuildRawPkg);
34
35impl fmt::Debug for EbuildRawPkg {
36 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
37 write!(f, "EbuildRawPkg {{ {self} }}")
38 }
39}
40
41impl EbuildRawPkg {
42 pub(crate) fn try_new(cpv: Cpv, repo: &EbuildRepo) -> crate::Result<Self> {
43 let relpath = cpv.relpath();
44 let data =
45 fs::read_to_string(repo.path().join(&relpath)).map_err(|e| Error::InvalidPkg {
46 cpv: Box::new(cpv.clone()),
47 repo: repo.to_string(),
48 err: Box::new(Error::InvalidValue(format!(
49 "failed reading ebuild: {relpath}: {e}"
50 ))),
51 })?;
52
53 let eapi = Self::parse_eapi(&data).map_err(|error| Error::InvalidPkg {
54 cpv: Box::new(cpv.clone()),
55 repo: repo.to_string(),
56 err: Box::new(error),
57 })?;
58
59 let chksum = repo.metadata().cache().chksum(&data);
60 let repo = repo.clone();
61 let tree = Default::default();
62 Ok(Self(Arc::new(InternalEbuildRawPkg {
63 cpv,
64 repo,
65 eapi,
66 data: data.into(),
67 chksum,
68 tree,
69 })))
70 }
71
72 fn parse_eapi(data: &str) -> crate::Result<&'static Eapi> {
74 let s = data
75 .filter_lines()
76 .next()
77 .and_then(|(_, s)| s.strip_prefix("EAPI="))
78 .map(|s| {
79 s.split_once('#')
80 .map(|(v, _)| v.trim())
81 .unwrap_or_else(|| s.trim())
82 })
83 .unwrap_or("0");
84
85 eapi::parse_value(s)?.parse()
86 }
87
88 pub fn relpath(&self) -> Utf8PathBuf {
90 self.0.cpv.relpath()
91 }
92
93 pub fn path(&self) -> Utf8PathBuf {
95 self.0.repo.path().join(self.relpath())
96 }
97
98 pub fn data(&self) -> &str {
100 &self.0.data
101 }
102
103 pub fn chksum(&self) -> &str {
105 &self.0.chksum
106 }
107
108 pub fn tree(&self) -> &bash::Tree {
110 self.0.tree.get_or_init(|| {
111 let data = Arc::clone(&self.0.data);
113 bash::Tree::new(data)
114 })
115 }
116
117 fn get_metadata(&self) -> crate::Result<Metadata> {
119 self.0
120 .repo
121 .metadata()
122 .cache()
123 .get(self)
124 .ok_or_else(|| Error::InvalidValue(format!("{self}: missing metadata entry")))
125 .flatten()
126 .and_then(|entry| entry.to_metadata(self))
127 }
128
129 pub(crate) fn metadata(&self, regen_on_failure: bool) -> crate::Result<Metadata> {
131 self.get_metadata().or_else(|e| {
132 if regen_on_failure {
133 self.0
134 .repo
135 .pool()
136 .metadata_task(&self.0.repo)
137 .force(true)
138 .run(&self.0.cpv)?;
139 self.get_metadata()
140 } else {
141 Err(e)
142 }
143 })
144 }
145
146 pub fn env(&self) -> crate::Result<IndexMap<String, String>> {
148 let repo = &self.0.repo;
149 self.0.repo.pool().env(repo, &self.0.cpv)
150 }
151
152 pub fn duration(&self) -> crate::Result<Duration> {
154 let repo = &self.0.repo;
155 self.0.repo.pool().duration(repo, &self.0.cpv)
156 }
157}
158
159impl Package for EbuildRawPkg {
160 fn eapi(&self) -> &'static Eapi {
161 self.0.eapi
162 }
163
164 fn cpv(&self) -> &Cpv {
165 &self.0.cpv
166 }
167}
168
169impl RepoPackage for EbuildRawPkg {
170 type Repo = EbuildRepo;
171
172 fn repo(&self) -> Self::Repo {
173 self.0.repo.clone()
174 }
175}
176
177impl Intersects<Dep> for EbuildRawPkg {
178 fn intersects(&self, dep: &Dep) -> bool {
179 bool_not_equal!(self.cpn(), dep.cpn());
180
181 if dep.slot_dep().is_some() {
182 return false;
183 }
184
185 if dep.use_deps().is_some() {
186 return false;
187 }
188
189 if let Some(val) = dep.repo() {
190 bool_not_equal!(self.0.repo.name(), val);
191 }
192
193 if let Some(val) = dep.version() {
194 self.cpv().version().intersects(val)
195 } else {
196 true
197 }
198 }
199}
200
201#[cfg(test)]
202mod tests {
203 use crate::config::Config;
204 use crate::eapi::EAPI8;
205 use crate::repo::ebuild::EbuildRepoBuilder;
206 use crate::test::test_data;
207
208 use super::*;
209
210 #[test]
211 fn display_and_debug() {
212 let data = test_data();
213 let repo = data.ebuild_repo("metadata").unwrap();
214 let pkg = repo.iter_raw().next().unwrap().unwrap();
215 let s = pkg.to_string();
216 assert!(format!("{pkg:?}").contains(&s));
217 }
218
219 #[test]
220 fn relpath() {
221 let data = test_data();
222 let repo = data.ebuild_repo("metadata").unwrap();
223 let raw_pkg = repo.get_pkg_raw("optional/none-8").unwrap();
224 assert_eq!(raw_pkg.relpath(), "optional/none/none-8.ebuild");
225 }
226
227 #[test]
228 fn path() {
229 let data = test_data();
230 let repo = data.ebuild_repo("metadata").unwrap();
231 let raw_pkg = repo.get_pkg_raw("optional/none-8").unwrap();
232 assert_eq!(raw_pkg.path(), repo.path().join("optional/none/none-8.ebuild"));
233 }
234
235 #[test]
236 fn data() {
237 let mut config = Config::default();
238 let mut temp = EbuildRepoBuilder::new().build().unwrap();
239 let repo = config.add_repo(&temp).unwrap().into_ebuild().unwrap();
240 config.finalize().unwrap();
241
242 let data = indoc::indoc! {r#"
243 EAPI=8
244 DESCRIPTION="testing data content"
245 SLOT=0
246 "#};
247 temp.create_ebuild_from_str("cat/pkg-1", data).unwrap();
248 let raw_pkg = repo.get_pkg_raw("cat/pkg-1").unwrap();
249 assert_eq!(raw_pkg.data(), data);
250 assert!(!raw_pkg.chksum().is_empty());
251 }
252
253 #[test]
254 fn traits() {
255 let data = test_data();
256 let repo = data.ebuild_repo("metadata").unwrap();
257 let raw_pkg = repo.get_pkg_raw("optional/none-8").unwrap();
258 assert_eq!(raw_pkg.eapi(), &*EAPI8);
259 assert_eq!(raw_pkg.cpv().to_string(), "optional/none-8");
260 assert_eq!(&raw_pkg.repo(), repo);
261 }
262
263 #[test]
264 fn intersects_dep() {
265 let data = test_data();
266 let repo = data.ebuild_repo("commands").unwrap();
267 let raw_pkg = repo.get_pkg_raw("cat/pkg-1").unwrap();
268
269 for (s, expected) in [
270 ("cat/pkg", true),
271 ("=cat/pkg-0", false),
272 ("=cat/pkg-1", true),
273 ("cat/pkg:0", false),
274 ("cat/pkg:0/1", false),
275 ("cat/pkg[u]", false),
276 ("cat/pkg::test", false),
277 ("cat/pkg::commands", true),
278 ] {
279 let dep: Dep = s.parse().unwrap();
280 assert_eq!(raw_pkg.intersects(&dep), expected, "failed for {s}");
281 }
282 }
283}