[arch-projects] [namcap] [PATCH] Add py_mtime rule

Jelle van der Waa jelle at vdwaa.nl
Thu Aug 28 14:11:35 EDT 2014


On 08/27/14 at 01:18pm, Kyle Keen wrote:
> Signed-off-by: Kyle Keen <keenerd at gmail.com>
> ---
>  Namcap/rules/__init__.py |   1 +
>  Namcap/rules/py_mtime.py | 129 +++++++++++++++++++++++++++++++++++++++++++++++
>  Namcap/util.py           |  28 ++++++++++
>  namcap-tags              |   3 ++
>  4 files changed, 161 insertions(+)
>  create mode 100644 Namcap/rules/py_mtime.py
> 
> diff --git a/Namcap/rules/__init__.py b/Namcap/rules/__init__.py
> index f7780d2..8dc4e68 100644
> --- a/Namcap/rules/__init__.py
> +++ b/Namcap/rules/__init__.py
> @@ -42,6 +42,7 @@ from . import (
>    missingbackups,
>    perllocal,
>    permissions,
> +  py_mtime,
>    rpath,
>    scrollkeeper,
>    shebangdepends,
> diff --git a/Namcap/rules/py_mtime.py b/Namcap/rules/py_mtime.py
> new file mode 100644
> index 0000000..aaff238
> --- /dev/null
> +++ b/Namcap/rules/py_mtime.py
> @@ -0,0 +1,129 @@
> +#
> +# namcap rules - py_mtime
> +# Copyright (C) 2013 Kyle Keen <keenerd at gmail.com>
> +#
> +#   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
> +#
> +
> +"""
> +Check for py timestamps that are ahead of pyc/pyo timestamps
> +"""
> +
> +import os
> +from Namcap.util import load_mtree
> +from Namcap.ruleclass import *
> +
> +def _quick_filter(names):
> +	"can this package be skipped outright"
> +	if not names:
> +		return True
> +	found_py  = any(n.endswith('.py')  for n in names)
> +	found_pyc = any(n.endswith('.pyc') for n in names)
> +	found_pyo = any(n.endswith('.pyo') for n in names)
> +	if found_py and found_pyc:
> +		return False
> +	if found_py and found_pyo:
> +		return False
> +	return True
> +
> +def _tar_timestamps(tar):
> +	"takes a tar object"
> +	return dict((m.name, m.mtime) for m in tar.getmembers())

Since pyalpm is a Python 3 library, we can use dictionary comprehension
here and in the other cases where you use dict().

> +
> +def _mtree_timestamps(tar):
> +	"takes a tar object"
> +	return dict((h, a['time']) for h,a in load_mtree(tar) if 'time' in a)
> +
> +def _generic_timestamps(tar):
> +	"works for mtree and tar"
> +	if '.MTREE' in tar.getnames():
> +		return _mtree_timestamps(tar)
> +	return _tar_timestamps(tar)
> +
> +def _try_mtree(tar):
> +	"returns True if good, False if bad, None if N/A"
> +	if '.MTREE' not in tar.getnames():
> +		return None
> +	stamps = _mtree_timestamps(tar)
> +	if _quick_filter(stamps.keys()):
> +		return True
> +	return not _mtime_filter(stamps)
> +
> +def _try_tar(tar):
> +	"returns True if good, False if bad"
> +	names = tar.getnames()
> +	if _quick_filter(names):
> +		return True
> +	mtimes = _tar_timestamps(tar)
> +	return not _mtime_filter(mtimes)
> +
> +def _split_all(path):
> +	"like os.path.split but splits every directory"
> +	p2 = path
> +	dirs = []
> +	while p2 and p2 != '/':
> +		p2,p3 = os.path.split(p2)
> +		dirs.insert(0, p3)
> +	#dirs.insert(0, '/')
> +	return dirs
> +
> +def _source_py(path):
> +	"given a pyc/pyo, return the source path"
> +	if not path.endswith('.pyc') and not path.endswith('.pyo'):
> +		return None
> +	path = path[:-1]
> +	# handle py2
> +	if '__pycache__' not in path:
> +		return path
> +	# handle py3
> +	splitup = _split_all(path)
> +	if splitup[-2] != '__pycache__':
> +		return None
> +	splitup.pop(-2)
> +	f = splitup[-1]
> +	f = f.split('.')
> +	f.pop(-2)
> +	splitup[-1] = '.'.join(f)
> +	return os.path.join(*splitup)
> +
> +def _mtime_filter(mtimes):
> +	"return list of bad py file names"
> +	bad = []
> +	for name, mt2 in mtimes.items():
> +		if not name.endswith('.pyc') and not name.endswith('.pyo'):
> +			continue
> +		source_name = _source_py(name)
> +		if source_name not in mtimes:
> +			continue
> +		mt1 = mtimes[source_name]
> +		if mt1 > mt2:
> +			bad.append(source_name)
> +	return bad
> +
> +class package(TarballRule):
> +	name = "py_mtime"
> +	description = "Check for py timestamps that are ahead of pyc/pyo timestamps"
> +	def analyze(self, pkginfo, tar):
> +		mtree_status = _try_mtree(tar)
> +		tar_status = _try_tar(tar)
> +		if mtree_status == False and tar_status:
> +			# mtree only
> +			self.warning = [('py-mtime-mtree-warning', ())]
> +		elif not tar_status:
> +			# tar or both
> +			self.errors = [('py-mtime-tar-error', ())]
> +		self.infos = [('py-mtime-file-name %s', f[1:]) for f in _mtime_filter(_generic_timestamps(tar))]
> +
> +# vim: set ts=4 sw=4 noet:
> diff --git a/Namcap/util.py b/Namcap/util.py
> index 21d7163..0613202 100644
> --- a/Namcap/util.py
> +++ b/Namcap/util.py
> @@ -20,6 +20,7 @@
>  import os
>  import re
>  import stat
> +import gzip
>  
>  def _read_carefully(path, readcall):
>  	if not os.path.isfile(path):
> @@ -77,4 +78,31 @@ def script_type(path):
>  
>  clean_filename = lambda s: re.search(r"/tmp/namcap\.[0-9]*/(.*)", s).group(1)
>  
> +def _mtree_line(line):
> +	"returns head, {key:value}"
> +	# todo, un-hex the escaped chars
> +	head,_,kvs = line.partition(' ')
> +	kvs = dict(kv.split('=') for kv in kvs.split(' '))
> +	return head, kvs
> +
> +def load_mtree(tar):
> +	"takes a tar object, returns (path, {attributes})"
> +	if '.MTREE' not in tar.getnames():
> +		raise StopIteration
> +	zfile = tar.extractfile('.MTREE')
> +	text = gzip.open(zfile).read().decode("utf-8")
> +	defaults = {}
> +	for line in text.split('\n'):
> +		if not line:
> +			continue
> +		if line.startswith('#'):
> +			continue
> +		head, kvs = _mtree_line(line)
> +		if head == '/set':
> +			defaults = kvs
> +		attr = {}
> +		attr.update(defaults)
> +		attr.update(kvs)
> +		yield head, attr
> +
>  # vim: set ts=4 sw=4 noet:
> diff --git a/namcap-tags b/namcap-tags
> index d638478..8b67330 100644
> --- a/namcap-tags
> +++ b/namcap-tags
> @@ -67,6 +67,9 @@ perllocal-pod-present %s :: perllocal.pod found in %s.
>  pkgname-in-description :: Description should not contain the package name.
>  potential-non-fhs-info-page %s :: Potential non-FHS info page (%s) found.
>  potential-non-fhs-man-page %s :: Potential non-FHS man page (%s) found.
> +py-mtime-mtree-warning :: Found .py file newer (sub-second) than associated .pyc/pyo.
> +py-mtime-tar-error :: Found .py file newer than associated .pyc/pyo.
> +py-mtime-file-name %s :: Python script (%s) is newer than associated .pyc/pyo.
>  script-link-detected %s in %s :: Script link detected (%s) in file %s
>  scrollkeeper-dir-exists %s :: Scrollkeeper directory exists (%s). Remember to not run scrollkeeper till post_{install,upgrade,remove}.
>  site-ruby :: Found usr/lib/ruby/site_ruby in package, usr/lib/ruby/vendor_ruby should be used instead.
> -- 
> 2.0.1

Apart from the one comment, the code looks fine to me.

-- 
Jelle van der Waa
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 473 bytes
Desc: Digital signature
URL: <http://mailman.archlinux.org/pipermail/arch-projects/attachments/20140828/b9b9d4b6/attachment.asc>


More information about the arch-projects mailing list