intermediary commit
This commit is contained in:
223
arch/buildup/pkgchk.py
Executable file
223
arch/buildup/pkgchk.py
Executable file
@@ -0,0 +1,223 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import argparse
|
||||
import configparser
|
||||
import hashlib
|
||||
import os
|
||||
import re
|
||||
import shlex
|
||||
import subprocess
|
||||
import tarfile # for verifying built PKGBUILDs. We just need to grab <tar>/.PKGINFO, and check: pkgver = <version>
|
||||
import tempfile
|
||||
from collections import OrderedDict
|
||||
from urllib.request import urlopen
|
||||
|
||||
class color(object):
|
||||
PURPLE = '\033[95m'
|
||||
CYAN = '\033[96m'
|
||||
DARKCYAN = '\033[36m'
|
||||
BLUE = '\033[94m'
|
||||
GREEN = '\033[92m'
|
||||
YELLOW = '\033[93m'
|
||||
RED = '\033[91m'
|
||||
BOLD = '\033[1m'
|
||||
UNDERLINE = '\033[4m'
|
||||
END = '\033[0m'
|
||||
|
||||
|
||||
vcstypes = ('bzr', 'git', 'hg', 'svn')
|
||||
|
||||
class pkgChk(object):
|
||||
def __init__(self, pkg):
|
||||
# pkg should be a string of a PKGBUILD,
|
||||
# not the path to a file.
|
||||
self.pkg = pkg
|
||||
# The below holds parsed data from the PKGBUILD.
|
||||
self.pkgdata = {'pkgver': self.getLex('pkgver', 'var'),
|
||||
'_pkgver': self.getLex('_pkgver', 'var'),
|
||||
'pkgname': self.getLex('pkgname', 'var'),
|
||||
'sources': self.getLex('source', 'array')}
|
||||
|
||||
def getLex(self, attrib, attrtype):
|
||||
# Parse the PKGBUILD and return actual values from it.
|
||||
# attrtype should be "var" or "array".
|
||||
# var returns a string and array returns a list.
|
||||
# If the given attrib isn't in the pkgbuild, None is returned.
|
||||
# The sources array is special, though - it returns a tuple of:
|
||||
# (hashtype, dict) where dict is a mapping of:
|
||||
# filename: hash
|
||||
# filename2: hash2
|
||||
# etc.
|
||||
if attrtype not in ('var', 'array'):
|
||||
raise ValueError('{0} is not a valid attribute type.'.format(attrib))
|
||||
_sums = ('sha512', 'sha384', 'sha256', 'sha1', 'md5') # in order of preference
|
||||
_attrmap = {'var': 'echo ${{{0}}}'.format(attrib),
|
||||
'array': 'echo ${{{}[@]}}'.format(attrib)}
|
||||
_tempfile = tempfile.mkstemp(text = True)
|
||||
with open(_tempfile[1], 'w') as f:
|
||||
f.write(self.pkg)
|
||||
_cmd = ['/bin/bash',
|
||||
'--restricted', '--noprofile',
|
||||
'--init-file', _tempfile[1],
|
||||
'-i', '-c', _attrmap[attrtype]]
|
||||
with open(os.devnull, 'wb') as devnull:
|
||||
_out = subprocess.run(_cmd, env = {'PATH': ''},
|
||||
stdout = subprocess.PIPE,
|
||||
stderr = devnull).stdout.decode('utf-8').strip()
|
||||
if _out == '':
|
||||
os.remove(_tempfile[1])
|
||||
return(None)
|
||||
if attrtype == 'var':
|
||||
os.remove(_tempfile[1])
|
||||
return(_out)
|
||||
else: # it's an array
|
||||
if attrib == 'source':
|
||||
_sources = {}
|
||||
_source = shlex.split(_out)
|
||||
_sumarr = [None] * len(_source)
|
||||
for h in _sums:
|
||||
_cmd[-1] = 'echo ${{{0}[@]}}'.format(h + 'sums')
|
||||
with open(os.devnull, 'wb') as devnull:
|
||||
_out = subprocess.run(_cmd, env = {'PATH': ''},
|
||||
stdout = subprocess.PIPE,
|
||||
stderr = devnull).stdout.decode('utf-8').strip()
|
||||
if _out != '':
|
||||
os.remove(_tempfile[1])
|
||||
return(h, OrderedDict(zip(_source, shlex.split(_out))))
|
||||
else:
|
||||
continue
|
||||
# No match for checksums.
|
||||
os.remove(_tempfile[1])
|
||||
return(None, OrderedDict(zip(_source, shlex.split(_out))))
|
||||
else:
|
||||
os.remove(_tempfile[1])
|
||||
return(shlex.split(_out))
|
||||
return()
|
||||
|
||||
def getURL(self, url):
|
||||
with urlopen(url) as http:
|
||||
code = http.getcode()
|
||||
return(code)
|
||||
|
||||
def chkVer(self):
|
||||
_separators = []
|
||||
# TODO: this is to explicitly prevent parsing
|
||||
# VCS packages, so might need some re-tooling in the future.
|
||||
if self.pkgdata['pkgname'].split('-')[-1] in vcstypes:
|
||||
return(None)
|
||||
# transform the current version into a list of various components.
|
||||
if not self.pkgdata['pkgver']:
|
||||
return(None)
|
||||
if self.pkgdata['_pkgver']:
|
||||
_cur_ver = self.pkgdata['_pkgver']
|
||||
else:
|
||||
_cur_ver = self.pkgdata['pkgver']
|
||||
# This will catch like 90% of the software versions out there.
|
||||
# Unfortunately, it won't catch all of them. I dunno how to
|
||||
# handle that quite yet. TODO.
|
||||
_split_ver = _cur_ver.split('.')
|
||||
_idx = len(_split_ver) - 1
|
||||
while _idx >= 0:
|
||||
_url = re.sub('^[A-Za-z0-9]+::',
|
||||
'',
|
||||
list(self.pkgdata['sources'].keys())[0])
|
||||
_code = self.getURL(_url)
|
||||
_idx -= 1
|
||||
|
||||
def parseArgs():
|
||||
_ini = '~/.config/optools/buildup.ini'
|
||||
_defini = os.path.abspath(os.path.expanduser(_ini))
|
||||
args = argparse.ArgumentParser()
|
||||
args.add_argument('-c', '--config',
|
||||
default = _defini,
|
||||
dest = 'config',
|
||||
help = ('The path to the config file. ' +
|
||||
'Default: {0}{1}{2}').format(color.BOLD,
|
||||
_defini,
|
||||
color.END))
|
||||
args.add_argument('-R', '--no-recurse',
|
||||
action = 'store_false',
|
||||
dest = 'recurse',
|
||||
help = ('If specified, and the path provided is a directory, ' +
|
||||
'do NOT recurse into subdirectories.'))
|
||||
args.add_argument('-p', '--path',
|
||||
metavar = 'path/to/dir/or/PKGBUILD',
|
||||
default = None,
|
||||
dest = 'pkgpath',
|
||||
help = ('The path to either a directory containing PKGBUILDs (recursion ' +
|
||||
'enabled - see {0}-R/--no-recurse{1}) ' +
|
||||
'or a single PKGBUILD. Use to override ' +
|
||||
'the config\'s PKG:paths.').format(color.BOLD, color.END))
|
||||
return(args)
|
||||
|
||||
def parsePkg(pkgbuildstr):
|
||||
p = pkgChk(pkgbuildstr)
|
||||
p.chkVer()
|
||||
return()
|
||||
|
||||
def iterDir(pkgpath, recursion = True):
|
||||
filepaths = []
|
||||
if os.path.isfile(pkgpath):
|
||||
return([pkgpath])
|
||||
if recursion:
|
||||
for root, subdirs, files in os.walk(pkgpath):
|
||||
for vcs in vcstypes:
|
||||
if '.{0}'.format(vcs) in subdirs:
|
||||
subdirs.remove('.{0}'.format(vcs))
|
||||
for f in files:
|
||||
if 'PKGBUILD' in f:
|
||||
filepaths.append(os.path.join(root, f))
|
||||
else:
|
||||
for f in os.listdir(pkgpath):
|
||||
if 'PKGBUILD' in f:
|
||||
filepaths.append(f)
|
||||
filepaths.sort()
|
||||
return(filepaths)
|
||||
|
||||
def parseCfg(cfgfile):
|
||||
def getPath(p):
|
||||
return(os.path.abspath(os.path.expanduser(p)))
|
||||
_defcfg = '[PKG]\npaths = \ntestbuild = no\n[VCS]\n'
|
||||
for vcs in vcstypes:
|
||||
_defcfg += '{0} = no\n'.format(vcs)
|
||||
_cfg = configparser.ConfigParser()
|
||||
_cfg._interpolation = configparser.ExtendedInterpolation()
|
||||
_cfg.read((_defcfg, cfgfile))
|
||||
# We convert to a dict so we can do things like list comprehension.
|
||||
cfg = {s:dict(_cfg.items(s)) for s in _cfg.sections()}
|
||||
if 'paths' not in cfg['PKG'].keys():
|
||||
raise ValueError('You must provide a valid configuration ' +
|
||||
'file with the PKG:paths setting specified and valid.')
|
||||
cfg['PKG']['paths'] = sorted([getPath(p.strip()) for p in cfg['PKG']['paths'].split(',')],
|
||||
reverse = True)
|
||||
for p in cfg['PKG']['paths'][:]:
|
||||
if not os.path.exists(p):
|
||||
print('WARNING: {0} does not exist; skipping...'.format(p))
|
||||
cfg['PKG']['paths'].remove(p)
|
||||
# We also want to convert these to pythonic True/False
|
||||
cfg['PKG']['testbuild'] = _cfg['PKG'].getboolean('testbuild')
|
||||
for k in vcstypes:
|
||||
cfg['VCS'][k] = _cfg['VCS'].getboolean(k)
|
||||
return(cfg)
|
||||
|
||||
if __name__ == '__main__':
|
||||
args = vars(parseArgs().parse_args())
|
||||
if not os.path.isfile(args['config']):
|
||||
raise FileNotFoundError('{0} does not exist.'.format(cfg))
|
||||
cfg = parseCfg(args['config'])
|
||||
if args['pkgpath']:
|
||||
args['pkgpath'] = os.path.abspath(os.path.expanduser(args['pkgpath']))
|
||||
if os.path.isdir(args['pkgpath']):
|
||||
iterDir(args['pkgpath'], recursion = args['recurse'])
|
||||
elif os.path.isfile(args['pkgpath']):
|
||||
parsePkg(args['pkgpath'])
|
||||
else:
|
||||
raise FileNotFoundError('{0} does not exist.'.format(args['pkgpath']))
|
||||
else:
|
||||
files = []
|
||||
for p in cfg['PKG']['paths']:
|
||||
files.extend(iterDir(p))
|
||||
files.sort()
|
||||
for p in files:
|
||||
with open(p, 'r') as f:
|
||||
parsePkg(f.read())
|
||||
39
arch/buildup/sample.buildup.ini
Normal file
39
arch/buildup/sample.buildup.ini
Normal file
@@ -0,0 +1,39 @@
|
||||
## This configuration file will allow you to perform more
|
||||
## fine-grained control of BuildUp.
|
||||
## It supports the syntax shortcuts found here:
|
||||
## https://docs.python.org/3/library/configparser.html#configparser.ExtendedInterpolation
|
||||
|
||||
[PKG]
|
||||
# The path(s) to your PKGBUILD(s), or a directory/directories containing them.
|
||||
# If you have more than one, separate with a comma.
|
||||
paths = path/to/pkgbuilds,another/path/to/pkgbuilds
|
||||
|
||||
# If 'yes', try building the package with the new version.
|
||||
# If 'no' (the default), don't try to build with the new version.
|
||||
# This can be a good way to test that you don't need to modify the PKGBUILD,
|
||||
# but can be error-prone (missing makedeps, etc.).
|
||||
testbuild = no
|
||||
|
||||
[VCS]
|
||||
# Here you can enable or disable which VCS platforms you want to support.
|
||||
# Note that it will increase the time of your check, as it will
|
||||
# actually perform a checkout/clone/etc. of the source and check against
|
||||
# the version function inside the PKGBUILD.
|
||||
# It's also generally meaningless, as VCS PKGBUILDs are intended
|
||||
# to be dynamic. Nonetheless, the options are there.
|
||||
# Use 'yes' to enable, or 'no' to disable (the default).
|
||||
# Currently only the given types are supported (i.e. no CVS).
|
||||
|
||||
# THESE ARE CURRENTLY NOT SUPPORTED.
|
||||
|
||||
# Check revisions for -git PKGBUILDs
|
||||
git = no
|
||||
|
||||
# Check revisions for -svn PKGBUILDs
|
||||
svn = no
|
||||
|
||||
# Check revisions for -hg PKGBUILDs
|
||||
hg = no
|
||||
|
||||
# Check revisions for -bzr PKGBUILDs
|
||||
bzr = no
|
||||
@@ -8,7 +8,11 @@ import pprint
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
cfgfile = os.path.join(os.environ['HOME'], '.arch.repoclone.ini')
|
||||
cfgfile = os.path.join(os.environ['HOME'],
|
||||
'.config',
|
||||
'optools',
|
||||
'repoclone',
|
||||
'arch.ini')
|
||||
|
||||
# Rsync options
|
||||
opts = [
|
||||
@@ -108,6 +112,8 @@ def parseArgs():
|
||||
help = ('The upstream mirror to sync from, must be an rsync URI '+
|
||||
'(Default: {0}').format(liveopts['mirror']))
|
||||
# TODO: can we do this?
|
||||
# We can; we need to .format() a repo in, probably, on the src and dest.
|
||||
# Problem is the last updated/last synced files.
|
||||
# args.add_argument('-r',
|
||||
# '--repos',
|
||||
# dest = 'repos',
|
||||
|
||||
Reference in New Issue
Block a user