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 && abtype != "pep517" && abtype != "python" {
58 debug!(
59 "Explicit ABTYPE '{abtype}' is not Python, skipping PEP-517 lints"
60 );
61 continue;
62 }
63
64 let [pkgdep, builddep] = ["PKGDEP", "BUILDDEP"].map(|var| {
65 apml.with_upgraded(|apml| {
66 apml.ctx().map(|ctx| {
67 ctx.get(var)
68 .map(|val| val.as_string())
69 .unwrap_or_default()
70 })
71 })
72 .map(StringArray::from)
73 });
74 let (mut pkgdep, mut builddep) = (pkgdep?, builddep?);
75 let mut pkgdep_dirty = false;
76
77 for dep in &mut py_deps {
78 if let Some(prov_pkg) =
79 depsolver::find_system_package(dep, &pkgdep, &builddep)
80 .await?
81 {
82 if pkgdep.contains(&prov_pkg)
83 || (dep.build_dep && builddep.contains(&prov_pkg))
84 {
85 continue;
86 }
87
88 apml.with_upgraded(|apml| {
89 LintMessage::new(PYTHON_SUGGEST_DEP_LINT)
90 .snippet(Snippet::new_variable(
91 sess,
92 apml,
93 if dep.build_dep {
94 "BUILDDEP"
95 } else {
96 "PKGDEP"
97 },
98 ))
99 .note(format!(
100 "package '{prov_pkg}' provides {} dependency '{}'",
101 if dep.build_dep { "build" } else { "runtime" },
102 dep.name,
103 ))
104 .note(format!(
105 "requirement '{}' found in {}",
106 dep.raw_req, dep.origin,
107 ))
108 .emit(sess);
109
110 if !sess.dry {
111 if !dep.build_dep {
112 pkgdep.push(prov_pkg.clone());
113 } else {
114 builddep.push(prov_pkg.clone());
115 }
116 pkgdep_dirty = true;
117 }
118 });
119 }
120 }
121 if pkgdep_dirty {
122 apml.with_upgraded(|apml| {
123 apml.with_editor(|apml| {
124 apml.replace_var_lst(
125 "PKGDEP",
126 lst::VariableValue::String(pkgdep.print().into()),
127 );
128 apml.replace_var_lst(
129 "BUILDDEP",
130 lst::VariableValue::String(builddep.print().into()),
131 );
132 })
133 });
134 }
135 }
136 Ok(())
137 }
138}