libpfu_fixers/python/
deps.rs1use anyhow::Result;
4use async_trait::async_trait;
5use libabbs::apml::{lst, value::array::StringArray};
6use libpfu::{
7 Linter, Session, declare_lint, declare_linter,
8 message::{LintMessage, Snippet},
9 walk_defines,
10};
11use log::debug;
12
13use crate::python::depsolver;
14
15declare_linter! {
16 pub PYTHON_DEPS_LINTER,
17 PythonDepsLinter,
18 [
19 "python-suggested-dep",
20 ]
21}
22
23declare_lint! {
24 pub PYTHON_SUGGEST_DEP_LINT,
25 "python-suggested-dep",
26 Note,
27 "some dependencies may be missed"
28}
29
30#[async_trait]
31impl Linter for PythonDepsLinter {
32 async fn apply(&self, sess: &Session) -> Result<()> {
33 if sess.offline {
34 return Ok(());
35 }
36 let mut py_deps = depsolver::collect_deps(sess).await?;
37 if py_deps.is_empty() {
38 debug!(
39 "{:?} does not have any Python dependencies found",
40 sess.package
41 );
42 return Ok(());
43 } else {
44 debug!(
45 "Collected Python dependencies of {:?}: {:?}",
46 sess.package, py_deps
47 );
48 }
49
50 for mut apml in walk_defines(sess) {
51 debug!("Checking Python dependencies for {:?}", apml);
52 let abtype = apml.with_upgraded(|apml| {
53 apml.ctx()
54 .map(|ctx| ctx.get("ABTYPE").map(|val| val.as_string()))
55 })?;
56 if let Some(abtype) = abtype {
57 if abtype != "pep517" && abtype != "python" {
58 debug!(
59 "Explicit ABTYPE '{abtype}' is not Python, skipping PEP-517 lints"
60 );
61 continue;
62 }
63 }
64
65 let [pkgdep, builddep] = ["PKGDEP", "BUILDDEP"].map(|var| {
66 apml.with_upgraded(|apml| {
67 apml.ctx().map(|ctx| {
68 ctx.get(var)
69 .map(|val| val.as_string())
70 .unwrap_or_default()
71 })
72 })
73 .map(StringArray::from)
74 });
75 let (mut pkgdep, mut builddep) = (pkgdep?, builddep?);
76 let mut pkgdep_dirty = false;
77
78 for dep in &mut py_deps {
79 if let Some(prov_pkg) =
80 depsolver::find_system_package(dep, &pkgdep, &builddep)
81 .await?
82 {
83 if pkgdep.contains(&prov_pkg)
84 || (dep.build_dep && builddep.contains(&prov_pkg))
85 {
86 continue;
87 }
88
89 apml.with_upgraded(|apml| {
90 LintMessage::new(PYTHON_SUGGEST_DEP_LINT)
91 .snippet(Snippet::new_variable(
92 sess,
93 apml,
94 if dep.build_dep {
95 "BUILDDEP"
96 } else {
97 "PKGDEP"
98 },
99 ))
100 .note(format!(
101 "package '{prov_pkg}' provides {} dependency '{}'",
102 if dep.build_dep { "build" } else { "runtime" },
103 dep.name,
104 ))
105 .note(format!(
106 "requirement '{}' found in {}",
107 dep.raw_req, dep.origin,
108 ))
109 .emit(sess);
110
111 if !sess.dry {
112 if !dep.build_dep {
113 pkgdep.push(prov_pkg.clone());
114 } else {
115 builddep.push(prov_pkg.clone());
116 }
117 pkgdep_dirty = true;
118 }
119 });
120 }
121 }
122 if pkgdep_dirty {
123 apml.with_upgraded(|apml| {
124 apml.with_editor(|apml| {
125 apml.replace_var_lst(
126 "PKGDEP",
127 lst::VariableValue::String(pkgdep.print().into()),
128 );
129 apml.replace_var_lst(
130 "BUILDDEP",
131 lst::VariableValue::String(builddep.print().into()),
132 );
133 })
134 });
135 }
136 }
137 Ok(())
138 }
139}