This commit is contained in:
Syl 2014-02-08 21:04:00 +01:00
commit 610d0d59f2
3 changed files with 265 additions and 99 deletions

View File

@ -35,10 +35,39 @@ def package(ctx):
ctx.make_tarfile(tar, inputs) ctx.make_tarfile(tar, inputs)
ctx.make_tarfile('build/noarch.tarfile', ['head.h', 'waf_pouet.py']) ctx.make_tarfile('build/noarch.tarfile', ['head.h', 'waf_pouet.py'])
def test_download(ctx): # testing..........................................................................................
import urllib #
data = urllib.urlencode([('pkgname', APPNAME), ('pkgver', VERSION), ('pkgfile', 'noarch.tarfile')])
def hook(a, b, c): from waflib import Utils
pass class problem(object):
urllib.urlretrieve('http://localhost:8000/cgi-bin/download.py', 'x', hook, data) def __init__(self):
self.reader = distnet.package_reader()
#self.reader.debug = 1
self.vtable = {self.reader.myproject : [self.reader.myversion]}
def add_constraint(self, pkgname, pkgver, text):
self.reader.cache_constraints[(pkgname, pkgver)] = distnet.parse_constraints(text)
def set_versions(self, pkgname, versions):
self.vtable[pkgname] = Utils.to_list(versions)
def test(ctx):
p = problem()
p.add_constraint(APPNAME, VERSION, 'app1,1.0.*\napp2,1.0.*')
p.add_constraint('app1', '1.0.0', 'app3,1.0.*,')
p.add_constraint('app2', '1.0.0', 'app3,2.0.*,')
p.add_constraint('app3', '1.0.0', '')
p.add_constraint('app3', '2.0.0', '')
p.set_versions('app1', '1.0.0')
p.set_versions('app2', '1.0.0')
p.set_versions('app3', '1.0.0 2.0.0')
versions, constraints = p.reader.solve(p.vtable, {}, p.reader.myproject, p.reader.myversion)
#print versions
#print constraints
#for a, b in constraints.items():
# print a, b
p.reader.constraints = p.reader.solution_to_constraints(versions, constraints)
p.reader.check_errors()

View File

@ -17,9 +17,12 @@ from waflib.extras import distnet
form = cgi.FieldStorage() form = cgi.FieldStorage()
text = form.getvalue('text') text = form.getvalue('text')
distnet.packages.local_resolve(text) distnet.packages.debug = 0
distnet.packages.constraints = distnet.packages.local_resolve(text)
results = distnet.packages.get_results()
print '''Content-Type: text/plain print "Content-Type: text/plain"
print ""
%s''' % distnet.packages.get_results() print ""
print results

View File

@ -12,6 +12,7 @@ Caching files from a server has advantages over a NFS/Samba shared folder:
""" """
import os, urllib, tarfile, re, shutil, tempfile, sys import os, urllib, tarfile, re, shutil, tempfile, sys
from collections import OrderedDict
from waflib import Context, Utils, Logs from waflib import Context, Utils, Logs
try: try:
@ -40,10 +41,17 @@ except ImportError:
DISTNETCACHE = os.environ.get('DISTNETCACHE', '/tmp/distnetcache') DISTNETCACHE = os.environ.get('DISTNETCACHE', '/tmp/distnetcache')
DISTNETSERVER = os.environ.get('DISTNETSERVER', 'http://localhost:8000/cgi-bin/') DISTNETSERVER = os.environ.get('DISTNETSERVER', 'http://localhost:8000/cgi-bin/')
TARFORMAT = 'w:bz2' TARFORMAT = 'w:bz2'
TIMEOUT=60 TIMEOUT = 60
REQUIRES = 'requires.txt'
re_com = re.compile('\s*#.*', re.M) re_com = re.compile('\s*#.*', re.M)
def total_version_order(num):
lst = num.split('.')
template = '%10s' * len(lst)
ret = template % tuple(lst)
return ret
def get_distnet_cache(): def get_distnet_cache():
return getattr(Context.g_module, 'DISTNETCACHE', DISTNETCACHE) return getattr(Context.g_module, 'DISTNETCACHE', DISTNETCACHE)
@ -77,8 +85,8 @@ class package(Context.Context):
Context.Context.execute(self) Context.Context.execute(self)
pkgfile = send_package_name() pkgfile = send_package_name()
if not pkgfile in files: if not pkgfile in files:
if not 'requires.txt' in files: if not REQUIRES in files:
files.append('requires.txt') files.append(REQUIRES)
self.make_tarfile(pkgfile, files, add_to_package=False) self.make_tarfile(pkgfile, files, add_to_package=False)
def make_tarfile(self, filename, files, **kw): def make_tarfile(self, filename, files, **kw):
@ -129,98 +137,219 @@ class publish(Context.Context):
if data != 'ok': if data != 'ok':
self.fatal('Could not publish the package %r' % data) self.fatal('Could not publish the package %r' % data)
class constraint(object):
def __init__(self, line=''):
self.required_line = line
self.info = []
class pkg(object): line = line.strip()
pass if not line:
# name foo return
# version 1.0.0
# required_version 1.0.*
# localfolder /tmp/packages/foo/1.0/
class package_reader(object): lst = line.split(',')
def read_packages(self, filename='requires.txt'): if lst:
self.compute_dependencies(filename) self.pkgname = lst[0]
self.required_version = lst[1]
def read_package_string(self, txt):
if txt is None:
Logs.error('Hahaha, None!')
self.pkg_list = []
txt = re.sub(re_com, '', txt)
lines = txt.splitlines()
for line in lines:
if not line:
continue
p = pkg()
p.required_line = line
lst = line.split(',')
p.name = lst[0]
p.requested_version = lst[1]
self.pkg_list.append(p)
for k in lst: for k in lst:
a, b, c = k.partition('=') a, b, c = k.partition('=')
if a and c: if a and c:
setattr(p, a, c) self.info.append((a, c))
def __str__(self):
buf = []
buf.append(self.pkgname)
buf.append(self.required_version)
for k in self.info:
buf.append('%s=%s' % k)
return ','.join(buf)
def compute_dependencies(self, filename='requires.txt'): def __repr__(self):
return "requires %s-%s" % (self.pkgname, self.required_version)
def human_display(self, pkgname, pkgver):
return '%s-%s requires %s-%s' % (pkgname, pkgver, self.pkgname, self.required_version)
def why(self):
ret = []
for x in self.info:
if x[0] == 'reason':
ret.append(x[1])
return ret
def add_reason(self, reason):
self.info.append(('reason', reason))
def parse_constraints(text):
assert(text is not None)
constraints = []
text = re.sub(re_com, '', text)
lines = text.splitlines()
for line in lines:
line = line.strip()
if not line:
continue
constraints.append(constraint(line))
return constraints
def list_package_versions(cachedir, pkgname):
pkgdir = os.path.join(cachedir, pkgname)
try:
versions = os.listdir(pkgdir)
except OSError:
return []
versions.sort(key=total_version_order)
versions.reverse()
return versions
class package_reader(Context.Context):
cmd = 'solver'
fun = 'solver'
def __init__(self, **kw):
Context.Context.__init__(self, **kw)
self.myproject = getattr(Context.g_module, 'APPNAME', 'project')
self.myversion = getattr(Context.g_module, 'VERSION', '1.0')
self.cache_constraints = {}
self.constraints = []
def compute_dependencies(self, filename=REQUIRES):
text = Utils.readf(filename) text = Utils.readf(filename)
data = safe_urlencode([('text', text)]) data = safe_urlencode([('text', text)])
req = Request(get_resolve_url(), data) if '--offline' in sys.argv:
try: self.constraints = self.local_resolve(text)
response = urlopen(req, timeout=TIMEOUT)
except URLError as e:
Logs.warn('The package server is down! %r' % e)
self.local_resolve(text)
else: else:
ret = response.read() req = Request(get_resolve_url(), data)
try: try:
ret = ret.decode('utf-8') response = urlopen(req, timeout=TIMEOUT)
except Exception: except URLError as e:
pass Logs.warn('The package server is down! %r' % e)
print(ret) self.constraints = self.local_resolve(text)
self.read_package_string(ret) else:
ret = response.read()
try:
ret = ret.decode('utf-8')
except Exception:
pass
self.trace(ret)
self.constraints = parse_constraints(ret)
self.check_errors()
def check_errors(self):
errors = False errors = False
for p in self.pkg_list: for c in self.constraints:
if getattr(p, 'error', ''): if not c.required_version:
Logs.error(p.error)
errors = True errors = True
reasons = c.why()
if len(reasons) == 1:
Logs.error('%s but no matching package could be found in this repository' % reasons[0])
else:
Logs.error('Conflicts on package %r:' % c.pkgname)
for r in reasons:
Logs.error(' %s' % r)
if errors: if errors:
raise ValueError('Requirements could not be satisfied!') self.fatal('The package requirements cannot be satisfied!')
def load_constraints(self, pkgname, pkgver, requires=REQUIRES):
try:
return self.cache_constraints[(pkgname, pkgver)]
except KeyError:
#Logs.error("no key %r" % (pkgname, pkgver))
text = Utils.readf(os.path.join(get_distnet_cache(), pkgname, pkgver, requires))
ret = parse_constraints(text)
self.cache_constraints[(pkgname, pkgver)] = ret
return ret
def apply_constraint(self, domain, constraint):
vname = constraint.required_version.replace('*', '.*')
rev = re.compile(vname, re.M)
ret = [x for x in domain if rev.match(x)]
return ret
def trace(self, *k):
if getattr(self, 'debug', None):
Logs.error(*k)
def solve(self, packages_to_versions={}, packages_to_constraints={}, pkgname='', pkgver='', todo=[], done=[]):
# breadth first search
n_packages_to_versions = dict(packages_to_versions)
n_packages_to_constraints = dict(packages_to_constraints)
self.trace("calling solve with %r %r %r" % (packages_to_versions, todo, done))
done = done + [pkgname]
constraints = self.load_constraints(pkgname, pkgver)
self.trace("constraints %r" % constraints)
for k in constraints:
try:
domain = n_packages_to_versions[k.pkgname]
except KeyError:
domain = list_package_versions(get_distnet_cache(), k.pkgname)
self.trace("constraints?")
if not k.pkgname in done:
todo = todo + [k.pkgname]
self.trace("domain before %s -> %s, %r" % (pkgname, k.pkgname, domain))
# apply the constraint
domain = self.apply_constraint(domain, k)
self.trace("domain after %s -> %s, %r" % (pkgname, k.pkgname, domain))
n_packages_to_versions[k.pkgname] = domain
# then store the constraint applied
constraints = list(packages_to_constraints.get(k.pkgname, []))
constraints.append((pkgname, pkgver, k))
n_packages_to_constraints[k.pkgname] = constraints
if not domain:
self.trace("no domain while processing constraint %r from %r %r" % (domain, pkgname, pkgver))
return (n_packages_to_versions, n_packages_to_constraints)
# next package on the todo list
if not todo:
return (n_packages_to_versions, n_packages_to_constraints)
n_pkgname = todo[0]
n_pkgver = n_packages_to_versions[n_pkgname][0]
tmp = dict(n_packages_to_versions)
tmp[n_pkgname] = [n_pkgver]
self.trace("fixed point %s" % n_pkgname)
return self.solve(tmp, n_packages_to_constraints, n_pkgname, n_pkgver, todo[1:], done)
def get_results(self): def get_results(self):
buf = [] return '\n'.join([str(c) for c in self.constraints])
for x in self.pkg_list:
buf.append('%s,%s' % (x.name, x.requested_version)) def solution_to_constraints(self, versions, constraints):
for y in ('error', 'version'): solution = []
if hasattr(x, y): for p in versions.keys():
buf.append(',%s=%s' % (y, getattr(x, y))) c = constraint()
buf.append('\n') solution.append(c)
return ''.join(buf)
c.pkgname = p
if versions[p]:
c.required_version = versions[p][0]
else:
c.required_version = ''
for (from_pkgname, from_pkgver, c2) in constraints.get(p, ''):
c.add_reason(c2.human_display(from_pkgname, from_pkgver))
return solution
def local_resolve(self, text): def local_resolve(self, text):
self.read_package_string(text) self.cache_constraints[(self.myproject, self.myversion)] = parse_constraints(text)
for p in self.pkg_list: p2v = OrderedDict({self.myproject: [self.myversion]})
(versions, constraints) = self.solve(p2v, {}, self.myproject, self.myversion, [])
return self.solution_to_constraints(versions, constraints)
pkgdir = os.path.join(get_distnet_cache(), p.name) def download_to_file(self, pkgname, pkgver, subdir, tmp):
try: data = safe_urlencode([('pkgname', pkgname), ('pkgver', pkgver), ('pkgfile', subdir)])
versions = os.listdir(pkgdir)
except OSError:
p.error = 'Directory %r does not exist' % pkgdir
continue
vname = p.requested_version.replace('*', '.*')
rev = re.compile(vname, re.M)
versions = [x for x in versions if rev.match(x)]
versions.sort()
try:
p.version = versions[0]
except IndexError:
p.error = 'There is no package that satisfies %r %r' % (p.name, p.requested_version)
def download_to_file(self, p, subdir, tmp):
data = safe_urlencode([('pkgname', p.name), ('pkgver', p.version), ('pkgfile', subdir)])
req = urlopen(get_download_url(), data, timeout=TIMEOUT) req = urlopen(get_download_url(), data, timeout=TIMEOUT)
with open(tmp, 'wb') as f: with open(tmp, 'wb') as f:
while True: while True:
@ -241,20 +370,21 @@ class package_reader(object):
except Exception: except Exception:
pass pass
def get_pkg_dir(self, pkg, subdir): def get_pkg_dir(self, pkgname, pkgver, subdir):
pkgdir = os.path.join(get_distnet_cache(), pkg.name, pkg.version) pkgdir = os.path.join(get_distnet_cache(), pkgname, pkgver)
if not os.path.isdir(pkgdir): if not os.path.isdir(pkgdir):
os.makedirs(pkgdir) os.makedirs(pkgdir)
target = os.path.join(pkgdir, subdir) target = os.path.join(pkgdir, subdir)
if os.path.exists(target): if os.path.exists(target):
return target return target
(fd, tmp) = tempfile.mkstemp(dir=pkgdir) (fd, tmp) = tempfile.mkstemp(dir=pkgdir)
try: try:
os.close(fd) os.close(fd)
self.download_to_file(pkg, subdir, tmp) self.download_to_file(pkgname, pkgver, subdir, tmp)
if subdir == 'requires.txt': if subdir == REQUIRES:
os.rename(tmp, target) os.rename(tmp, target)
else: else:
self.extract_tar(subdir, pkgdir, tmp) self.extract_tar(subdir, pkgdir, tmp)
@ -267,28 +397,32 @@ class package_reader(object):
return target return target
def __iter__(self): def __iter__(self):
if not hasattr(self, 'pkg_list'): if not self.constraints:
self.read_packages()
self.compute_dependencies() self.compute_dependencies()
for x in self.pkg_list: for x in self.constraints:
if x.pkgname == self.myproject:
continue
yield x yield x
raise StopIteration raise StopIteration
def execute(self):
self.compute_dependencies()
packages = package_reader() packages = package_reader()
def load_tools(ctx, extra): def load_tools(ctx, extra):
global packages global packages
for pkg in packages: for c in packages:
packages.get_pkg_dir(pkg, extra) packages.get_pkg_dir(c.pkgname, c.required_version, extra)
noarchdir = packages.get_pkg_dir(pkg, 'noarch') noarchdir = packages.get_pkg_dir(c.pkgname, c.required_version, 'noarch')
#sys.path.append(noarchdir)
for x in os.listdir(noarchdir): for x in os.listdir(noarchdir):
if x.startswith('waf_') and x.endswith('.py'): if x.startswith('waf_') and x.endswith('.py'):
ctx.load(x.rstrip('.py'), tooldir=noarchdir) ctx.load([x.rstrip('.py')], tooldir=[noarchdir])
def options(opt): def options(opt):
packages.read_packages() opt.add_option('--offline', action='store_true')
load_tools(opt, 'requires.txt') packages.execute()
load_tools(opt, REQUIRES)
def configure(conf): def configure(conf):
load_tools(conf, conf.variant) load_tools(conf, conf.variant)