libpfu_fixers/python/
pep517.rs

1//! PEP-517 checks.
2
3use anyhow::Result;
4use async_trait::async_trait;
5use libabbs::apml::{ast, 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
13declare_linter! {
14	pub PEP517_LINTER,
15	Pep517Linter,
16	[
17		"upgrade-to-pep517",
18		"pep517-nopython2",
19		"pep517-python2-dep",
20		"pep517-python3-dep",
21	]
22}
23
24declare_lint! {
25	pub UPGRADE_TO_PEP517_LINT,
26	"upgrade-to-pep517",
27	Warning,
28	"use PEP-517 build backend"
29}
30
31declare_lint! {
32	pub PEP517_NOPYTHON2_LINT,
33	"pep517-nopython2",
34	Error,
35	"PEP-517 build template requires NOPYTHON2=1"
36}
37
38declare_lint! {
39	pub PEP517_PYTHON2_DEP_LINT,
40	"pep517-python2-dep",
41	Warning,
42	"python-2 should not be included in dependencies of PEP-517 package"
43}
44
45declare_lint! {
46	pub PEP517_PYTHON3_DEP_LINT,
47	"pep517-python3-dep",
48	Error,
49	"python-3 must be included as a runtime dependency of PEP-517 package"
50}
51
52#[async_trait]
53impl Linter for Pep517Linter {
54	async fn apply(&self, sess: &Session) -> Result<()> {
55		if !sess.offline
56			&& sess.source_fs().await?.exists("pyproject.toml").await?
57		{
58			debug!(
59				"pyproject.toml found, checking PEP-517 lints for {:?}",
60				sess.package
61			);
62
63			for mut apml in walk_defines(sess) {
64				let abtype = apml.with_upgraded(|apml| {
65					apml.ctx()
66						.map(|ctx| ctx.get("ABTYPE").map(|val| val.as_string()))
67				})?;
68				if let Some(abtype) = abtype {
69					if abtype == "python" {
70						apml.with_upgraded(|apml| {
71							LintMessage::new(UPGRADE_TO_PEP517_LINT)
72								.note("remove ABTYPE=python to allow automatic template detection".to_string())
73								.snippet(Snippet::new_variable(sess, apml, "ABTYPE"))
74								.emit(sess);
75							if !sess.dry {
76								apml.with_editor(|apml| {
77									apml.remove_var(
78										apml.find_var_index("ABTYPE").unwrap(),
79									)
80								})
81							}
82						})
83					} else if abtype != "pep517" {
84						debug!(
85							"Explicit ABTYPE '{abtype}' is not pep517, skipping PEP-517 lints"
86						);
87						continue;
88					}
89				}
90
91				let nopy2 = apml.with_upgraded(|apml| {
92					apml.ctx()
93						.map(|ctx| ctx.read("NOPYTHON2").into_string() == "1")
94				})?;
95				if !nopy2 {
96					LintMessage::new(PEP517_NOPYTHON2_LINT)
97						.snippet(Snippet::new_index(sess, &apml, 0))
98						.emit(sess);
99					if !sess.dry {
100						apml.with_upgraded(|apml| {
101							apml.with_editor(|apml| {
102								apml.append_var_ast(
103									"NOPYTHON2".to_string(),
104									&ast::VariableValue::String(
105										ast::Text::from("1"),
106									),
107									Some("ABTYPE"),
108								);
109							})
110						})
111					}
112				}
113
114				let pkgdep = apml.with_upgraded(|apml| {
115					apml.ctx().map(|ctx| {
116						ctx.get("PKGDEP")
117							.map(|val| val.as_string())
118							.unwrap_or_default()
119					})
120				})?;
121				let mut pkgdep = StringArray::from(pkgdep);
122				let mut pkgdep_dirty = false;
123
124				if pkgdep.iter().any(|dep| dep == "python-2") {
125					apml.with_upgraded(|apml| {
126						LintMessage::new(PEP517_PYTHON2_DEP_LINT)
127							.snippet(Snippet::new_variable(
128								sess, apml, "PKGDEP",
129							))
130							.emit(sess);
131					});
132					if !sess.dry {
133						let pos = pkgdep
134							.iter()
135							.position(|dep| dep == "python-2")
136							.unwrap();
137						pkgdep.remove(pos);
138						pkgdep_dirty = true;
139					}
140				}
141				if !pkgdep.iter().any(|dep| dep == "python-3") {
142					apml.with_upgraded(|apml| {
143						LintMessage::new(PEP517_PYTHON3_DEP_LINT)
144							.snippet(Snippet::new_variable(
145								sess, apml, "PKGDEP",
146							))
147							.emit(sess);
148					});
149					if !sess.dry {
150						pkgdep.push("python-3".to_string());
151						pkgdep_dirty = true;
152					}
153				}
154				if pkgdep_dirty {
155					apml.with_upgraded(|apml| {
156						apml.with_editor(|apml| {
157							apml.replace_var_lst(
158								"PKGDEP",
159								lst::VariableValue::String(
160									pkgdep.print().into(),
161								),
162							);
163						})
164					});
165				}
166			}
167		}
168		Ok(())
169	}
170}