---
 Namcap/rules/__init__.py               |   1 +
 Namcap/rules/pydepends.py              | 122 +++++++++++++++++++++++++
 Namcap/tests/package/test_pydepends.py |  68 ++++++++++++++
 3 files changed, 191 insertions(+)
 create mode 100644 Namcap/rules/pydepends.py
 create mode 100644 Namcap/tests/package/test_pydepends.py

diff --git a/Namcap/rules/__init__.py b/Namcap/rules/__init__.py
index 525dbc6..01d1b96 100644
--- a/Namcap/rules/__init__.py
+++ b/Namcap/rules/__init__.py
@@ -43,6 +43,7 @@ from . import (
   perllocal,
   permissions,
   py_mtime,
+  pydepends,
   rpath,
   scrollkeeper,
   shebangdepends,
diff --git a/Namcap/rules/pydepends.py b/Namcap/rules/pydepends.py
new file mode 100644
index 0000000..efc6735
--- /dev/null
+++ b/Namcap/rules/pydepends.py
@@ -0,0 +1,122 @@
+# -*- coding: utf-8 -*-
+#
+# namcap rules - pydepends
+# Copyright (C) 2020 Felix Yan <felixonm...@archlinux.org>
+#
+#   This program is free software; you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation; either version 2 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program; if not, write to the Free Software
+#   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+#
+
+from collections import defaultdict
+import ast
+import sys
+import sysconfig
+import Namcap.package
+from Namcap.ruleclass import *
+
+
+def finddepends(liblist):
+       """
+       Find packages owning a list of libraries
+
+       Returns:
+         dependlist -- a dictionary { package => set(libraries) }
+         orphans -- the list of libraries without owners
+       """
+       dependlist = defaultdict(set)
+
+       pymatches = {}
+
+       knownlibs = set(liblist)
+       foundlibs = set()
+
+       workarounds = {
+               "python": sys.builtin_module_names
+       }
+
+       for pkg in Namcap.package.get_installed_packages():
+               for j, fsize, fmode in pkg.files:
+                       if not j.startswith("usr/lib/python3"):
+                               continue
+
+                       for k in knownlibs:
+                               if j.endswith("site-packages/" + k + "/") or 
j.endswith("site-packages/" + k + ".py") or \
+                                               j.endswith("site-packages/" + k 
+ ".so") or \
+                                               j.endswith("site-packages/" + k 
+ sysconfig.get_config_var('EXT_SUFFIX')) or \
+                                               j.endswith("lib-dynload/" + k + 
sysconfig.get_config_var('EXT_SUFFIX')) or \
+                                               j.count("/") == 3 and 
j.endswith("/" + k + ".py") or \
+                                               j.count("/") == 4 and 
j.endswith("/" + k + "/") or \
+                                               pkg.name in workarounds and k 
in workarounds[pkg.name]:
+                                       dependlist[pkg.name].add(k)
+                                       foundlibs.add(k)
+
+       orphans = list(knownlibs - foundlibs)
+       return dependlist, orphans
+
+
+def get_imports(file):
+       root = ast.parse(file.read())
+
+       for node in ast.walk(root):
+               if isinstance(node, ast.Import):
+                       for module in node.names:
+                               yield module.name.split('.')[0]
+               elif isinstance(node, ast.ImportFrom):
+                       if node.module and node.level == 0:
+                               yield node.module.split('.')[0]
+
+
+class PythonDependencyRule(TarballRule):
+       name = "pydepends"
+       description = "Checks python dependencies"
+       def analyze(self, pkginfo, tar):
+               liblist = defaultdict(set)
+               own_liblist = set()
+
+               for entry in tar:
+                       if not entry.isfile() or not entry.name.endswith('.py'):
+                               continue
+                       own_liblist.add(entry.name[:-3])
+                       f = tar.extractfile(entry)
+                       for module in get_imports(f):
+                               liblist[module].add(entry.name)
+                       f.close()
+
+               for lib in own_liblist:
+                       liblist.pop(lib, None)
+
+               dependlist, orphans = finddepends(liblist)
+
+               # Handle "no package associated" errors
+               self.warnings.extend([("library-no-package-associated %s", i)
+                       for i in orphans])
+
+               # Print link-level deps
+               for pkg, libraries in dependlist.items():
+                       if isinstance(libraries, set):
+                               files = list(libraries)
+                               needing = set().union(*[liblist[lib] for lib in 
libraries])
+                               reasons = pkginfo.detected_deps.setdefault(pkg, 
[])
+                               reasons.append((
+                                       "libraries-needed %s %s",
+                                       (str(files), str(list(needing)))
+                                       ))
+                               self.infos.append(("link-level-dependence %s in 
%s", (pkg, str(files))))
+
+               # Check for packages in testing
+               for i in dependlist.keys():
+                       p = Namcap.package.load_testing_package(i)
+                       q = Namcap.package.load_from_db(i)
+                       if p is not None and q is not None and p["version"] == 
q["version"] :
+                               
self.warnings.append(("dependency-is-testing-release %s", i))
diff --git a/Namcap/tests/package/test_pydepends.py 
b/Namcap/tests/package/test_pydepends.py
new file mode 100644
index 0000000..bb0dfbb
--- /dev/null
+++ b/Namcap/tests/package/test_pydepends.py
@@ -0,0 +1,68 @@
+# -*- coding: utf-8 -*-
+#
+# namcap tests - pydepends
+# Copyright (C) 2020 Felix Yan <felixonm...@archlinux.org>
+#
+#   This program is free software; you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation; either version 2 of the License, or
+#   (at your option) any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program; if not, write to the Free Software
+#   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
+#   USA
+#
+
+import os
+from Namcap.tests.makepkg import MakepkgTest
+import Namcap.rules.pydepends
+
+
+class PyDependsTest(MakepkgTest):
+       pkgbuild = """
+pkgname=__namcap_test_pydepends
+pkgver=1.0
+pkgrel=1
+pkgdesc="A package"
+arch=('any')
+url="http://www.example.com/";
+license=('GPL')
+depends=('python-six')
+source=()
+build() {
+  cd "${srcdir}"
+  echo "import six, pyalpm" > main.py
+}
+package() {
+  install -D -m 755 "$srcdir/main.py" "$pkgdir/usr/bin/main.py"
+}
+"""
+       def test_pydepends(self):
+               "Package with missing pacman dependency"
+               pkgfile = "__namcap_test_pydepends-1.0-1-any.pkg.tar"
+               with open(os.path.join(self.tmpdir, "PKGBUILD"), "w") as f:
+                       f.write(self.pkgbuild)
+               self.run_makepkg()
+               pkg, r = self.run_rule_on_tarball(
+                               os.path.join(self.tmpdir, pkgfile),
+                               Namcap.rules.pydepends.PythonDependencyRule
+                               )
+               self.assertEqual(pkg.detected_deps['pyalpm'], [
+                       ('libraries-needed %s %s',
+                        (str(["pyalpm"]), str(["usr/bin/main.py"])))
+                       ]
+               )
+               e, w, i = Namcap.depends.analyze_depends(pkg)
+               self.assertEqual(e, [
+                       ('dependency-detected-not-included %s (%s)',
+                               ('pyalpm', "libraries ['pyalpm'] needed in 
files ['usr/bin/main.py']"))
+               ])
+               self.assertEqual(w, [])
+
+# vim: set ts=4 sw=4 noet:
-- 
2.24.1

Reply via email to