# vi: encoding=utf-8 ts=8 sts=4 sw=4 et
from xml.dom.minidom import *
from datetime import datetime
import string
import time
import xml.sax.saxutils
import fnmatch
import os
import urllib
import html
import pytz
import tempfile
import subprocess
import util
import log
from acl import acl
from config import config
__all__ = ['parse_request', 'parse_requests']
def text(e):
res = ""
for n in e.childNodes:
if n.nodeType != Element.TEXT_NODE:
log.panic("xml: text expected in <%s>, got %d" % (e.nodeName, n.nodeType))
res += n.nodeValue
return res
def attr(e, a, default = None):
try:
return e.attributes[a].value
except:
if default != None:
return default
raise
def escape(s):
return xml.sax.saxutils.escape(s)
# return timestamp with timezone information
# so we could parse it in javascript
def tzdate(t):
# as strftime %z is unofficial, and does not work, need to make it numeric ourselves
date = time.strftime("%a %b %d %Y %H:%M:%S", time.localtime(t))
# NOTE: the altzone is showing CURRENT timezone, not what the "t" reflects
# NOTE: when DST is off timezone gets it right, altzone not
if time.daylight:
tzoffset = time.altzone
else:
tzoffset = time.timezone
tz = '%+05d' % (-tzoffset / 3600 * 100)
return date + ' ' + tz
# return date in iso8601 format
def iso8601(ts, timezone='UTC'):
tz = pytz.timezone(timezone)
dt = datetime.fromtimestamp(ts, tz)
return dt.isoformat()
def is_blank(e):
return e.nodeType == Element.TEXT_NODE and e.nodeValue.strip() == ""
class Group:
def __init__(self, e):
self.batches = []
self.kind = 'group'
self.id = attr(e, "id")
self.no = int(attr(e, "no"))
self.priority = 2
self.time = time.time()
self.requester = ""
self.max_jobs = 0
self.requester_email = ""
self.flags = attr(e, "flags", "").split()
for c in e.childNodes:
if is_blank(c): continue
if c.nodeType != Element.ELEMENT_NODE:
log.panic("xml: evil group child %d" % c.nodeType)
if c.nodeName == "batch":
self.batches.append(Batch(c))
elif c.nodeName == "requester":
self.requester = text(c)
self.requester_email = attr(c, "email", "")
elif c.nodeName == "priority":
self.priority = int(text(c))
elif c.nodeName == "time":
self.time = int(text(c))
elif c.nodeName == "maxjobs":
self.max_jobs = int(text(c))
else:
log.panic("xml: evil group child (%s)" % c.nodeName)
# note that we also check that group is sorted WRT deps
m = {}
for b in self.batches:
deps = []
m[b.b_id] = b
for dep in b.depends_on:
if dep in m:
# avoid self-deps
if id(m[dep]) != id(b):
deps.append(m[dep])
else:
log.panic("xml: dependency not found in group")
b.depends_on = deps
if self.requester_email == "" and self.requester != "":
self.requester_email = acl.user(self.requester).mail_to()
def dump(self, f):
f.write("group: %d (id=%s pri=%d)\n" % (self.no, self.id, self.priority))
f.write(" from: %s\n" % self.requester)
f.write(" flags: %s\n" % ' '.join(self.flags))
f.write(" time: %s\n" % time.asctime(time.localtime(self.time)))
for b in self.batches:
b.dump(f)
f.write("\n")
# return structure usable for json encoding
def dump_json(self):
batches = []
for b in self.batches:
batches.append(b.dump_json())
return dict(
no=self.no,
id=self.id,
time=self.time,
requester=self.requester,
priority=self.priority,
max_jobs=self.max_jobs,
flags=self.flags,
batches=batches,
)
def dump_html(self, f):
f.write(
"
\n" % ' '.join(builders))
def rpmbuild_opts(self):
"""
return all rpmbuild options related to this build
"""
rpmopts = self.bconds_string() + self.kernel_string() + self.target_string() + self.defines_string()
rpmdefs = \
"--define '_topdir %s' " % self.get_topdir() + \
"--define '_specdir %{_topdir}' " \
"--define '_sourcedir %{_specdir}' " \
"--define '_rpmdir %{_topdir}/RPMS' " \
"--define '_builddir %{_topdir}/BUILD' "
return rpmdefs + rpmopts
def php_ignores(self, php_version):
# available php versions in distro
php_versions = ['7.2', '7.3', '7.4', '8.0']
# remove current php version
try:
php_versions.remove(php_version)
except ValueError:
log.notice("Attempt to remove inexistent key '%s' from %s" % (php_version, php_versions))
pass
# map them to poldek ignores
# always ignore hhvm
res = ['hhvm-*']
for v in list(map(php_ver_to_name, php_versions)):
res.append("php%s-*" % v)
return res
# build ignore package list
# currently only php ignore is filled based on build context
def ignores(self):
ignores = []
# add php version based ignores
if 'php_suffix' in self.defines:
# current version if -D php_suffix is present
php_version = php_name_to_ver(self.defines['php_suffix'])
else:
php_version = self.DEFAULT_PHP
ignores.extend(self.php_ignores(php_version))
# return empty string if the list is empty
if len(ignores) == 0:
return ""
def add_ignore(s):
return "--ignore=%s" % s
return " ".join(list(map(add_ignore, ignores)))
def kernel_string(self):
r = ""
if self.kernel != "":
r = " --define 'alt_kernel " + self.kernel + "'"
return r
def target_string(self):
if len(self.target) > 0:
return " --target " + ",".join(self.target)
else:
return ""
def bconds_string(self):
r = ""
for b in self.bconds_with:
r = r + " --with " + b
for b in self.bconds_without:
r = r + " --without " + b
return r
def defines_string(self):
r = ""
for key,value in self.defines.items():
r += " --define '%s %s'" % (key, value)
return r
def defines_xml(self):
r = ""
for key,value in self.defines.items():
r += "%s\n" % (escape(key), escape(value))
return r
def default_target(self, arch):
self.target.append("%s-tld-linux" % arch)
def write_to(self, f):
f.write("""
%s%s%s%s%s\n""" % (self.b_id,
' '.join(list(map(lambda b: b.b_id, self.depends_on))),
escape(self.src_rpm),
escape(' '.join(self.command_flags)), escape(self.command),
escape(self.spec), escape(self.branch), escape(self.info)))
if self.kernel != "":
f.write(" %s\n" % escape(self.kernel))
for b in self.bconds_with:
f.write(" %s\n" % escape(b))
for b in self.target:
f.write(" %s\n" % escape(b))
for b in self.bconds_without:
f.write(" %s\n" % escape(b))
if self.defines:
f.write(" %s\n" % self.defines_xml())
for b in self.builders:
if b in self.builders_status_buildtime:
t = self.builders_status_buildtime[b]
else:
t = "0"
f.write(" %s\n" % \
(escape(self.builders_status[b]), self.builders_status_time[b], t, escape(b)))
f.write(" \n")
def log_line(self, l):
log.notice(l)
if self.logfile != None:
util.append_to(self.logfile, l)
def expand_builders(batch, all_builders):
all = []
for bld in batch.builders:
res = []
for my_bld in all_builders:
if fnmatch.fnmatch(my_bld, bld):
res.append(my_bld)
if res != []:
all.extend(res)
else:
all.append(bld)
batch.builders = all
class Notification:
def __init__(self, e):
self.batches = []
self.kind = 'notification'
self.group_id = attr(e, "group-id")
self.builder = attr(e, "builder")
self.batches = {}
self.batches_buildtime = {}
for c in e.childNodes:
if is_blank(c): continue
if c.nodeType != Element.ELEMENT_NODE:
log.panic("xml: evil notification child %d" % c.nodeType)
if c.nodeName == "batch":
id = attr(c, "id")
status = attr(c, "status")
buildtime = attr(c, "buildtime", "0")
if not status.startswith("OK") and not status.startswith("SKIP") and not status.startswith("UNSUPP") and not status.startswith("FAIL"):
log.panic("xml notification: bad status: %s" % status)
self.batches[id] = status
self.batches_buildtime[id] = buildtime
else:
log.panic("xml: evil notification child (%s)" % c.nodeName)
# return structure usable for json encoding
def dump_json(self):
return dict(
id=self.group_id,
builder=self.builder,
batches=self.batches,
batches_buildtime=self.batches_buildtime,
)
def apply_to(self, q):
for r in q.requests:
if r.kind == "group":
for b in r.batches:
if b.b_id in self.batches:
b.builders_status[self.builder] = self.batches[b.b_id]
b.builders_status_time[self.builder] = time.time()
b.builders_status_buildtime[self.builder] = "0" #self.batches_buildtime[b.b_id]
def build_request(e):
if e.nodeType != Element.ELEMENT_NODE:
log.panic("xml: evil request element")
if e.nodeName == "group":
return Group(e)
elif e.nodeName == "notification":
return Notification(e)
elif e.nodeName == "command":
# FIXME
return Command(e)
else:
log.panic("xml: evil request [%s]" % e.nodeName)
def parse_request(f):
d = parseString(f)
return build_request(d.documentElement)
def parse_requests(f):
d = parseString(f)
res = []
for r in d.documentElement.childNodes:
if is_blank(r): continue
res.append(build_request(r))
return res