jw-pkg/scripts/projects.py
Jan Lindemann 69de7c53c8 rpmdist.mk: Fix off-by-one bug in rpm-release-reinstall
Introduce --dont-expand-version-macros into projects.py, and use it
to postpone version expansion into pkg.sh.

Signed-off-by: Jan Lindemann <jan@janware.com>
2017-02-24 13:31:33 +00:00

528 lines
17 KiB
Python

#!/usr/bin/python -u
from __future__ import print_function
import sys
import argparse
from sets import Set
from os.path import isfile
from os.path import isdir
from os.path import expanduser
from os.path import basename
from os.path import realpath
import subprocess
import re
import platform
# meaning of pkg.required.xxx variables
# build: needs to be built and installed before this can be built
# devel: needs to be installed before this-devel can be installed, i.e. before _other_ packages can be built against this
# run: needs to be installed before this-run can be installed, i.e. before this and other packages can run with this
# --------------------------------------------------------------------- helpers
def debug(*objs):
if args.debug:
print("DEBUG: ", *objs, file=sys.stderr)
def err(*objs):
print("ERR: ", *objs, file=sys.stderr)
def proj_dir(name):
return projs_root + '/' + name
def re_section(name):
return re.compile('[' + name + ']'
'.*?'
'(?=[)',
re.DOTALL)
def remove_duplicates(seq):
seen = set()
seen_add = seen.add
return [x for x in seq if not (x in seen or seen_add(x))]
def get_os(args = ""):
for d in [ projs_root + 'ytools/devutil/scripts', '/opt/ytools/bin' ]:
script = d + '/get_os.sh'
if isfile(script):
cmd = '/bin/bash ' + script
if args:
cmd = cmd + ' ' + args
p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE)
(out, rr) = p.communicate()
if rr:
err("failed to run ", cmd)
continue
out = re.sub('\n', '', out)
return out
return "linux"
# TODO: add support for customizing this in project.conf
def htdocs_dir(name):
pd = proj_dir(name)
for r in [ pd + "/tools/html/htdocs", pd + "/htdocs", "/srv/www/proj/" + name
]:
if isdir(r):
return r
return None
def pkg_required_os_cascade():
os = get_os()
name = re.sub('-.*', '', os)
# e.g. os, linux, suse, suse-tumbleweed
return [ 'os', platform.system().lower(), name, os ]
def strip_module_from_spec(mod):
return re.sub(r'-devel$|-run$', '', re.split('([=><]+)', mod)[0].strip())
def get_section(path, section):
r = ''
file = open(path)
pat = '[' + section + ']'
in_section = False
for line in file:
if (line.rstrip() == pat):
in_section = True
continue
if in_section:
if len(line) and line[0] == '[':
break
r = r + line
file.close()
return r.rstrip()
def read_value(path, section, key):
debug("opening ", path)
try:
file = open(path)
except:
return None
r = []
if not len(section):
for line in file:
r = re.findall('^ *' + key + ' *= *(.*)', line)
if (len(r) > 0):
break
else:
in_section = False
pat = '[' + section + ']'
for line in file:
if (line.rstrip() == pat):
in_section = True
continue
if in_section:
if len(line) and line[0] == '[':
break
if key is None:
r.append(line)
else:
r = re.findall('^ *' + key + ' *= *(.*)', line)
if (len(r) > 0):
break
file.close()
if len(r):
return r[0]
return None
def get_value(name, section, key):
#print("top_name = ", top_name)
if top_name and name == top_name:
proj_root = topdir
else:
proj_root = projs_root + '/' + name
if section == 'version':
file = open(proj_root + '/VERSION', 'r')
r=file.read().replace('\n', '').replace('-dev', '')
file.close()
return r
path = proj_root + '/make/project.conf'
#print('path = ', path, 'top_name = ', top_name, 'name = ', name)
return read_value(path, section, key)
def collect_values(names, section, key):
r = ""
for n in names:
val = get_value(n, section, key)
if val:
r = r + " " + val
return remove_duplicates([x.strip() for x in r.split(",")])
# scope 0: no children
# scope 1: children
# scope 2: recursive
def add_modules_from_project_txt(buf, visited, spec, section, key, add_self, scope,
names_only):
name = strip_module_from_spec(spec)
if names_only:
spec = name
if spec in buf:
return
if spec in visited:
if add_self:
buf.append(spec)
return
visited.add(spec)
deps = get_value(name, section, key)
debug("name = ", name, "section = ", section, "key = ", key, "deps = ", deps, "scope = ", scope, "visited = ", visited)
if deps and scope > 0:
if scope == 1:
subscope = 0
else:
subscope = 2
deps = deps.split(',')
for dep in deps:
add_modules_from_project_txt(buf, visited, dep,
section, key, add_self=True, scope=subscope,
names_only=names_only)
if add_self:
buf.append(spec)
def get_modules_from_project_txt(names, section, keys, add_self, scope,
names_only = True):
if isinstance(keys, basestring):
keys = [ keys ]
#r = Set()
r = []
for key in keys:
visited = Set()
for name in names:
rr = []
add_modules_from_project_txt(rr, visited, name, section, key, add_self, scope,
names_only)
# TODO: this looks like a performance hogger
for m in rr:
if not m in r:
r.append(m)
return r
def get_libname(names):
vals = get_modules_from_project_txt(names, 'build', 'libname',
scope = 1, add_self=False, names_only=True)
if not vals:
return ' '.join(names)
if 'none' in vals:
vals.remove('none')
return ' '.join(reversed(vals))
# -L needs to contain more paths than libs linked with -l would require
def get_ldpathflags(names):
deps = get_modules_from_project_txt(names, 'pkg.required.jw', 'build',
scope = 2, add_self=True, names_only=True)
r = ''
for m in deps:
libname = get_libname([m])
if len(libname):
r = r + ' -L' + proj_dir(m) + '/lib'
print(r[1:])
def get_ldflags(names):
#print(names)
deps = get_modules_from_project_txt(names, 'pkg.required.jw', 'build',
scope = 1, add_self=False, names_only=True)
#print(deps)
r = ''
for m in reversed(deps):
libname = get_libname([m])
if len(libname):
#r = r + ' -L' + proj_dir(m) + '/lib -l' + libname
r = r + ' -l' + libname
if len(r):
ldpathflags = get_ldpathflags(names)
if ldpathflags:
r = ldpathflags + ' ' + r
return r[1::]
return ''
def commands():
f = open(sys.argv[0])
cmds = []
for line in f:
debug("checking line ", line)
rr = re.findall('^def *cmd_([a-z0-9_]+).*', line)
if len(rr):
cmds.append(rr[0].replace('_', '-'))
f.close()
return ' '.join(cmds)
# --------------------------------------------------------------------- commands
def cmd_commands(args_):
print(commands())
def cmd_test(args_):
parser = argparse.ArgumentParser(description='Test')
parser.add_argument('blah', default='', help='The blah argument')
args=parser.parse_args(args_)
print("blah = " + args.blah)
def cmd_required_pkg(args_):
parser = argparse.ArgumentParser(description='required-pkg')
parser.add_argument('module', nargs='*', help='Modules')
args=parser.parse_args(args_)
deps = get_modules_from_project_txt(args.module, 'pkg.required.jw', [ 'build' ],
scope = 2, add_self=True, names_only=True)
subsecs = pkg_required_os_cascade()
debug("subsecs = ", subsecs)
required = []
for s in subsecs:
vals = collect_values(deps, 'pkg.required.' + s, 'build')
if vals:
required = required + vals
# TODO: add all not in build tree as -devel
r = ''
for m in required:
r = r + ' ' + m
print(r[1:])
def cmd_ldlibpath(args_):
parser = argparse.ArgumentParser(description='ldlibpath')
parser.add_argument('module', nargs='*', help='Modules')
args=parser.parse_args(args_)
deps = get_modules_from_project_txt(args.module, 'pkg.required.jw', [ 'run', 'build' ],
scope = 2, add_self=True, names_only=True)
r = ''
for m in deps:
r = r + ':' + proj_dir(m) + '/lib'
print(r[1:])
def cmd_exepath(args_):
parser = argparse.ArgumentParser(description='exepath')
parser.add_argument('module', nargs='*', help='Modules')
args=parser.parse_args(args_)
deps = get_modules_from_project_txt(args.module, 'pkg.required.jw', 'run',
scope = 2, add_self=True, names_only=True)
r = ''
for m in deps:
r = r + ':' + proj_dir(m) + '/bin'
print(r[1:])
def cmd_libname(args_):
parser = argparse.ArgumentParser(description='libname')
parser.add_argument('module', nargs='*', help='Modules')
args=parser.parse_args(args_)
print(get_libname(args.module))
def cmd_ldflags(args_):
parser = argparse.ArgumentParser(description='ldlibpath')
parser.add_argument('module', nargs='*', help='Modules')
args=parser.parse_args(args_)
print(get_ldflags(args.module))
def cmd_cflags(args_):
parser = argparse.ArgumentParser(description='cflags')
parser.add_argument('module', nargs='*', help='Modules')
args=parser.parse_args(args_)
deps = get_modules_from_project_txt(args.module, 'pkg.required.jw', 'build',
scope = 2, add_self=True, names_only=True)
r = ''
for m in reversed(deps):
r = r + ' -I' + proj_dir(m) + '/include'
print(r[1:])
def cmd_path(args_):
parser = argparse.ArgumentParser(description='path')
parser.add_argument('module', nargs='*', help='Modules')
args=parser.parse_args(args_)
deps = get_modules_from_project_txt(args.module, 'pkg.required.jw', 'run',
scope = 2, add_self=True, names_only=True)
r = ''
for m in deps:
r = r + ':' + proj_dir(m) + '/bin'
print(r[1:])
def cmd_prereq(args_):
parser = argparse.ArgumentParser(description='path')
parser.add_argument('flavour', help='Flavour')
parser.add_argument('module', nargs='*', help='Modules')
args=parser.parse_args(args_)
deps = get_modules_from_project_txt(args.module, 'pkg.required.jw',
args.flavour, scope = 2, add_self=False, names_only=True)
print(' '.join(deps))
def cmd_pkg_requires(args_):
parser = argparse.ArgumentParser(description='pkg-requires')
# TODO: implement Vendor evaluation
parser.add_argument('--vendor', '-V', nargs='?', default='jw', help='Package Vendor')
parser.add_argument('flavour', help='Flavour')
parser.add_argument('module', nargs='*', help='Modules')
parser.add_argument('--dont-strip-revision', action='store_true',
default=False, help='Always treat VERSION macro as VERSION-REVISION')
parser.add_argument('--dont-expand-version-macros', action='store_true',
default=False, help='Don\'t expand VERSION and REVISION macros')
args=parser.parse_args(args_)
debug('flavour = ', args.flavour, ', vendor = ', args.vendor)
version_pattern=re.compile("[0-9-.]*")
r = []
for m in args.module:
value = get_value(m, 'pkg.required.jw', args.flavour)
if not value:
continue
deps = value.split(',')
for spec in deps:
dep = re.split('([=><]+)', spec)
for i, item in enumerate(dep):
dep[i] = item.strip()
if len(dep) == 3:
dep_project = re.sub(r'-devel$|-run$', '', dep[0])
if args.dont_expand_version_macros and dep_project in args.module:
version = dep[2]
else:
version = get_value(dep_project, 'version', '')
if dep[2] == 'VERSION':
if args.dont_strip_revision:
dep[2] = version
else:
dep[2] = version.split('-')[0]
elif dep[2] == 'VERSION-REVISION':
dep[2] = version
elif version_pattern.match(dep[2]):
# dep[2] = dep[2]
pass
else:
raise Exception("Unknown version specifier in " + spec)
r.append(' '.join(dep))
print(', '.join(r))
def cmd_proj_dir(args_):
parser = argparse.ArgumentParser(description='proj-dir')
parser.add_argument('module', nargs='*', help='Modules')
args=parser.parse_args(args_)
r = []
for m in args.module:
r.append(proj_dir(m))
print(' '.join(r))
def cmd_htdocs_dir(args_):
parser = argparse.ArgumentParser(description='htdocs-dir')
parser.add_argument('module', nargs='*', help='Modules')
args=parser.parse_args(args_)
r = []
for m in args.module:
r.append(htdocs_dir(m))
print(' '.join(r))
def cmd_summary(args_):
parser = argparse.ArgumentParser(description='summary')
parser.add_argument('module', nargs='*', help='Modules')
args=parser.parse_args(args_)
r = []
for m in args.module:
summary = get_value(m, "summary", None)
if summary is not None:
r.append(summary)
print(' '.join(r))
def contains(small, big):
for i in xrange(len(big)-len(small)+1):
for j in xrange(len(small)):
if big[i+j] != small[j]:
break
else:
return i, i+len(small)
return False
def read_dep_graph(modules, section, graph):
for m in modules:
if m in graph:
continue
deps = get_modules_from_project_txt([ m ], 'pkg.required.jw', section,
scope = 1, add_self=False, names_only=True)
if not deps is None:
graph[m] = deps
for d in deps:
read_dep_graph([ d ], section, graph)
def flip_graph(graph):
r = {}
for m, deps in graph.iteritems():
for d in deps:
if not d in r:
r[d] = Set()
r[d].add(m)
return r
def check_circular_deps(module, section, graph, unvisited, temp, path):
if module in temp:
debug('found circular dependency at module', module)
return module
if not module in unvisited:
return None
temp.add(module)
if module in graph:
for m in graph[module]:
last = check_circular_deps(m, section, graph, unvisited, temp, path)
if last is not None:
path.insert(0, m)
return last
unvisited.remove(module)
temp.remove(module)
def cmd_check(args_):
parser = argparse.ArgumentParser(description='check')
parser.add_argument('module', nargs='*', help='Modules')
parser.add_argument('--flavour', '-f', nargs='?', default = 'build')
args=parser.parse_args(args_)
graph = {}
path = []
read_dep_graph(args.module, args.flavour, graph)
unvisited = graph.keys()
temp = Set()
while len(unvisited) is not 0:
m = unvisited[0]
debug('checking circular dependency of', m)
last = check_circular_deps(m, args.flavour, flip_graph(graph), unvisited, temp, path)
if last is not None:
debug('found circular dependency below', m, ', last is', last)
print('found circular dependency in flavour', args.flavour, ':', ' -> '.join(path))
exit(1)
print('no circular dependency found for flavour', args.flavour, ' in modules:',
' '.join(args.module))
exit(0)
# -------------------------------------------------------------------- here we go
global_args = []
skip = 0
for a in sys.argv[1::]:
global_args.append(a)
if a in [ '--prefix', '-p', '--topdir', '-t' ]:
skip = 1
continue
if skip > 0:
skip = skip -1
continue
if a[0] != '-':
break
topdir = None
top_name = None
parser = argparse.ArgumentParser(description='Project metadata evaluation')
parser.add_argument('cmd', default='', help='Command')
parser.add_argument('--debug', '-d', action='store_true',
default=False, help='Output debug information to stderr')
parser.add_argument('--topdir', '-t', nargs='?', help='Project Path')
parser.add_argument('--prefix', '-p', nargs='?', default = expanduser("~") +
'/local/src/jw.dev/proj', help='Projects Path Prefix')
parser.add_argument('arg', nargs='*', help='Command arguments')
args=parser.parse_args(global_args)
debug("----------------------------------------- running ", ' '.join(sys.argv))
projs_root = args.prefix
if args.topdir:
topdir = args.topdir
top_name = read_value(topdir + '/make/project.conf', 'build', 'name')
if not top_name:
top_name = re.sub('-[0-9.-]*$', '', basename(realpath(topdir)))
cmd = getattr(sys.modules[__name__], 'cmd_' + args.cmd.replace('-', '_'))
cmd(sys.argv[(len(global_args) + 1)::])