Skip to content

Commit 9f2c866

Browse files
ktfdberzano
authored andcommitted
Improve and refactor aliDoctor (#295)
The package list creation logic has been abstracted out of aliBuild and now powers aliDoctor. This allows us to limit the check to the actual packages being built and not all of them. Moreover it also allows for checking the system requirements. Output is also prettified and human readable. Fixes #267.
1 parent d54a6ed commit 9f2c866

File tree

4 files changed

+221
-45
lines changed

4 files changed

+221
-45
lines changed

aliBuild

Lines changed: 1 addition & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import json
1212
import logging
1313
from alibuild_helpers.log import debug, error, warning, banner, info
1414
from alibuild_helpers.log import logger_handler, logger, LogFormatter, ProgressPrint, riemannStream
15-
from alibuild_helpers.utilities import doDetectArch, format, getVersion
15+
from alibuild_helpers.utilities import format, getVersion, detectArch
1616
from alibuild_helpers.analytics import decideAnalytics, askForAnalytics, report_screenview, report_exception, report_event
1717
from alibuild_helpers.analytics import enable_analytics, disable_analytics
1818

@@ -149,26 +149,6 @@ VALID_ARCHS_RE = ["slc[5-9]+_(x86-64|ppc64)",
149149
"(ubuntu|ubt|osx)[0-9]*_x86-64",
150150
]
151151

152-
# Try to guess a good platform. This does not try to cover all the
153-
# possibly compatible linux distributions, but tries to get right the
154-
# common one, obvious one. If you use a Unknownbuntu which is compatible
155-
# with Ubuntu 15.10 you will still have to give an explicit platform
156-
# string.
157-
#
158-
# FIXME: we should have a fallback for lsb_release, since platform.dist
159-
# is going away.
160-
def detectArch():
161-
hasOsRelease = exists("/etc/os-release")
162-
osReleaseLines = open("/etc/os-release").readlines() if hasOsRelease else []
163-
try:
164-
import platform
165-
platformTuple = platform.dist()
166-
platformSystem = platform.system()
167-
platformProcessor = platform.processor()
168-
return doDetectArch(hasOsRelease, osReleaseLines, platformTuple, platformSystem, platformProcessor)
169-
except:
170-
return None
171-
172152
# Detect number of available CPUs. Fallback to 1.
173153
def detectJobs():
174154
# Python 2.6+

aliDoctor

Lines changed: 109 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,20 @@ from os.path import exists, abspath
55
from glob import glob
66
import yaml
77
from commands import getstatusoutput
8+
import logging
9+
from alibuild_helpers.log import debug, error, banner, info, success, warning
10+
from alibuild_helpers.log import logger_handler, logger, LogFormatter, ProgressPrint
11+
from alibuild_helpers.utilities import getPackageList, format, detectArch
812
import subprocess
913

10-
def execute(command, prefix):
11-
print prefix + ":" + command.strip("\n")
14+
def execute(command):
1215
popen = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
1316
lines_iterator = iter(popen.stdout.readline, "")
17+
txt = ""
1418
for line in lines_iterator:
15-
print prefix + ":" +line.strip("\n") # yield line
16-
output = popen.communicate()[0]
17-
for x in output.split("\n"):
18-
print prefix + ":" + output
19-
print prefix + ": exit code " + str(popen.returncode)
19+
txt += line # yield line
20+
txt += popen.communicate()[0]
21+
return (popen.returncode, txt)
2022

2123
def prunePaths(workDir):
2224
for x in ["PATH", "LD_LIBRARY_PATH", "DYLD_LIBRARY_PATH"]:
@@ -25,10 +27,64 @@ def prunePaths(workDir):
2527
workDirEscaped = re.escape("%s" % workDir) + "[^:]*:?"
2628
os.environ[x] = re.sub(workDirEscaped, "", os.environ[x])
2729

30+
def checkPreferSystem(spec, cmd, homebrew_replacement):
31+
if cmd == "false":
32+
debug("Package %s can only be managed via alibuild." % spec["package"])
33+
return (1, "")
34+
cmd = homebrew_replacement + cmd
35+
err, out = execute(cmd)
36+
if not err:
37+
success("Package %s will be picked up from the system." % spec["package"])
38+
for x in out.split("\n"):
39+
debug(spec["package"] + ": " + x)
40+
return (err, "")
41+
42+
warning(format("Package %(p)s cannot be picked up from the system and will be built by aliBuild.\n"
43+
"This is due to the fact the following script fails:\n\n"
44+
"%(cmd)s\n\n"
45+
"with the following output:\n\n"
46+
"%(error)s\n",
47+
p=spec["package"],
48+
cmd=cmd,
49+
error="\n".join(["%s: %s" % (spec["package"],x) for x in out.split("\n")])))
50+
return (err, "")
51+
52+
def checkRequirements(spec, cmd, homebrew_replacement):
53+
if cmd == "false":
54+
debug("Package %s is not a system requirement." % spec["package"])
55+
return (0, "")
56+
cmd = homebrew_replacement + cmd
57+
err, out = execute(cmd)
58+
if not err:
59+
success("Required package %s will be picked up from the system." % spec["package"])
60+
debug(cmd)
61+
for x in out.split("\n"):
62+
debug(spec["package"] + ": " + x)
63+
return (0, "")
64+
error(format("Package %(p)s is a system requirement and cannot be found.\n"
65+
"This is due to the fact that the following script fails:\n\n"
66+
"%(cmd)s\n"
67+
"with the following output:\n\n"
68+
"%(error)s\n"
69+
"%(help)s\n",
70+
p=spec["package"],
71+
cmd=cmd,
72+
error="\n".join(["%s: %s" % (spec["package"],x) for x in out.split("\n")]),
73+
help=spec.get("system_requirement_missing")))
74+
return (err, "")
75+
2876
if __name__ == "__main__":
2977
parser = argparse.ArgumentParser()
30-
parser.add_argument("-c", "--config", help="path to alidist", dest="config", default="alidist")
31-
parser.add_argument("-w", "--work-dir", help="path to work dir", dest="workDir", default="workDir")
78+
parser.add_argument("-a", "--architecture", help="force architecture",
79+
dest="architecture", default=detectArch())
80+
parser.add_argument("-c", "--config", help="path to alidist",
81+
dest="config", default="alidist")
82+
parser.add_argument("-w", "--work-dir", help="path to work dir",
83+
dest="workDir", default="workDir")
84+
parser.add_argument("-d", "--debug", help="Show also successful tests.",
85+
dest="debug", action="store_true", default=False)
86+
parser.add_argument("packages", nargs="+", help="Package to test",
87+
default=[])
3288
args = parser.parse_args()
3389
if not exists(args.config):
3490
parser.error("Wrong path to alidist specified: %s" % args.config)
@@ -37,16 +93,49 @@ if __name__ == "__main__":
3793

3894
# Decide if we can use homebrew. If not, we replace it with "true" so
3995
# that we do not get spurious messages on linux
40-
homebrew_replacement = "brew"
96+
homebrew_replacement = ""
4197
err, output = getstatusoutput("which brew")
4298
if err:
43-
homebrew_replacement = "true"
44-
45-
for x in glob("%s/*.sh" % args.config):
46-
print "Reading", x
47-
txt = file(x).read()
48-
header, body = txt.split("---", 1)
49-
spec = yaml.safe_load(header)
50-
if "prefer_system_check" in spec:
51-
execute(spec["prefer_system_check"].replace("brew", homebrew_replacement),
52-
spec["package"])
99+
homebrew_replacement = "brew() { true; }; "
100+
101+
logger.setLevel(logging.BANNER)
102+
if args.debug:
103+
logger.setLevel(logging.DEBUG)
104+
105+
specs = {}
106+
packages = []
107+
for p in args.packages:
108+
path = "%s/%s.sh" % (args.config, p.lower())
109+
if not exists(path):
110+
error("Cannot find recipe %s for package %s." % (path, p))
111+
continue
112+
packages.append(p)
113+
114+
specs = {}
115+
def unreachable():
116+
assert(False)
117+
(fromSystem, own, failed) = getPackageList(packages=packages,
118+
specs=specs,
119+
configDir=args.config,
120+
preferSystem=False,
121+
noSystem=False,
122+
architecture=args.architecture,
123+
disable=[],
124+
defaults="release",
125+
dieOnError=lambda x, y : unreachable,
126+
performPreferCheck=lambda pkg, cmd : checkPreferSystem(pkg, cmd, homebrew_replacement),
127+
performRequirementCheck=lambda pkg, cmd : checkRequirements(pkg, cmd, homebrew_replacement))
128+
129+
if fromSystem:
130+
banner("The following packages will be picked up from the system:\n\n- " +
131+
"\n- ".join(fromSystem) +
132+
"\n\nIf this is not you want, you have to uninstall / unload them.")
133+
if own:
134+
banner("The following packages will be build by aliBuild because they couldn't be picked up from the system:\n\n- " +
135+
"\n- ".join(own) +
136+
"\n\nThis is not a real issue, but it might take longer the first time you invoke aliBuild.")
137+
if failed:
138+
banner("The following packages are system dependencies and could not be found:\n\n- "+
139+
"\n- ".join(failed)
140+
)
141+
exit(1)

alibuild_helpers/log.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import socket, time
55
from alibuild_helpers.utilities import format
66

7-
debug, error, warning, info, riemannStream = (None, None, None, None, None)
7+
debug, error, warning, info, success, riemannStream = (None, None, None, None, None, None)
88

99
# A stream object which will end up pushing data to a riemann server
1010
class RiemannStream(object):
@@ -66,7 +66,8 @@ def __init__(self, fmtstr):
6666
self.COLOR_RESET = "\033[m" if sys.stdout.isatty() else ""
6767
self.LEVEL_COLORS = { logging.WARNING: "\033[4;33m",
6868
logging.ERROR: "\033[4;31m",
69-
logging.CRITICAL: "\033[1;37;41m" } if sys.stdout.isatty() else {}
69+
logging.CRITICAL: "\033[1;37;41m",
70+
logging.SUCCESS: "\033[1;32m" } if sys.stdout.isatty() else {}
7071
def format(self, record):
7172
if record.levelno == logging.BANNER and sys.stdout.isatty():
7273
lines = str(record.msg).split("\n")
@@ -115,6 +116,14 @@ def log_banner(self, message, *args, **kws):
115116
self._log(logging.BANNER, message, args, **kws)
116117
logging.Logger.banner = log_banner
117118

119+
# Add loglevel SUCCESS (same as ERROR, but green)
120+
logging.SUCCESS = 45
121+
logging.addLevelName(logging.SUCCESS, "SUCCESS")
122+
def log_success(self, message, *args, **kws):
123+
if self.isEnabledFor(logging.SUCCESS):
124+
self._log(logging.SUCCESS, message, args, **kws)
125+
logging.Logger.success = log_success
126+
118127
logger = logging.getLogger('alibuild')
119128
logger_handler = logging.StreamHandler()
120129
logger.addHandler(logger_handler)
@@ -125,6 +134,7 @@ def log_banner(self, message, *args, **kws):
125134
warning = logger.warning
126135
info = logger.info
127136
banner = logger.banner
137+
success = logger.success
128138

129139
riemannStream = RiemannStream(host=getenv("RIEMANN_HOST"),
130140
port=getenv("RIEMANN_PORT", "5555"))

alibuild_helpers/utilities.py

Lines changed: 99 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
#!/usr/bin/env python
2-
import subprocess
2+
import subprocess, re, yaml
33
import pkg_resources # part of setuptools
44
from commands import getstatusoutput
5-
from os.path import dirname
5+
from os.path import dirname, exists
6+
import platform
67

78
def format(s, **kwds):
89
return s % kwds
@@ -48,10 +49,106 @@ def doDetectArch(hasOsRelease, osReleaseLines, platformTuple, platformSystem, pl
4849
v=version.split(".")[0],
4950
c=processor.replace("_", "-"))
5051

52+
# Try to guess a good platform. This does not try to cover all the
53+
# possibly compatible linux distributions, but tries to get right the
54+
# common one, obvious one. If you use a Unknownbuntu which is compatible
55+
# with Ubuntu 15.10 you will still have to give an explicit platform
56+
# string.
57+
#
58+
# FIXME: we should have a fallback for lsb_release, since platform.dist
59+
# is going away.
60+
def detectArch():
61+
hasOsRelease = exists("/etc/os-release")
62+
osReleaseLines = open("/etc/os-release").readlines() if hasOsRelease else []
63+
try:
64+
import platform
65+
platformTuple = platform.dist()
66+
platformSystem = platform.system()
67+
platformProcessor = platform.processor()
68+
return doDetectArch(hasOsRelease, osReleaseLines, platformTuple, platformSystem, platformProcessor)
69+
except:
70+
return None
71+
5172
def getVersion():
5273
try:
5374
return pkg_resources.require("alibuild")[0].version
5475
except:
5576
cmd = "GIT_DIR=\'%s/.git\' git describe --tags" % dirname(dirname(__file__))
5677
err, version = getstatusoutput(cmd)
5778
return version if not err else "Unknown version."
79+
80+
def filterByArchitecture(arch, requires):
81+
for r in requires:
82+
require, matcher = ":" in r and r.split(":", 1) or (r, ".*")
83+
if re.match(matcher, arch):
84+
yield require
85+
86+
def getPackageList(packages, specs, configDir, preferSystem, noSystem,
87+
architecture, disable, defaults, dieOnError, performPreferCheck, performRequirementCheck):
88+
systemPackages = set()
89+
ownPackages = set()
90+
failedRequirements = set()
91+
testCache = {}
92+
requirementsCache = {}
93+
while packages:
94+
p = packages.pop(0)
95+
if p in specs:
96+
continue
97+
try:
98+
d = open("%s/%s.sh" % (configDir, p.lower())).read()
99+
except IOError,e:
100+
dieOnError(True, str(e))
101+
header, recipe = d.split("---", 1)
102+
spec = yaml.safe_load(header)
103+
dieOnError(spec["package"].lower() != p.lower(),
104+
"%s.sh has different package field: %s" % (p, spec["package"]))
105+
# If --always-prefer-system is passed or if prefer_system is set to true
106+
# inside the recipe, use the script specified in the prefer_system_check
107+
# stanza to see if we can use the system version of the package.
108+
if not noSystem and (preferSystem or re.match(spec.get("prefer_system", "(?!.*)"), architecture)):
109+
cmd = spec.get("prefer_system_check", "false")
110+
if not spec["package"] in testCache:
111+
testCache[spec["package"]] = performPreferCheck(spec, cmd.strip())
112+
113+
err, output = testCache[spec["package"]]
114+
115+
if not err:
116+
systemPackages.update([spec["package"]])
117+
disable.append(spec["package"])
118+
else:
119+
ownPackages.update([spec["package"]])
120+
121+
dieOnError(("system_requirement" in spec) and recipe.strip("\n\t "),
122+
"System requirements %s cannot have a recipe" % spec["package"])
123+
if re.match(spec.get("system_requirement", "(?!.*)"), architecture):
124+
cmd = spec.get("system_requirement_check", "false")
125+
if not spec["package"] in requirementsCache:
126+
requirementsCache[spec["package"]] = performRequirementCheck(spec, cmd.strip())
127+
128+
err, output = requirementsCache[spec["package"]]
129+
if err:
130+
failedRequirements.update([spec["package"]])
131+
spec["version"] = "failed"
132+
else:
133+
disable.append(spec["package"])
134+
135+
if spec["package"] in disable:
136+
continue
137+
138+
# For the moment we treat build_requires just as requires.
139+
fn = lambda what: filterByArchitecture(architecture, spec.get(what, []))
140+
spec["requires"] = [x for x in fn("requires") if not x in disable]
141+
spec["build_requires"] = [x for x in fn("build_requires") if not x in disable]
142+
if spec["package"] != "defaults-" + defaults:
143+
spec["build_requires"].append("defaults-" + defaults)
144+
spec["runtime_requires"] = spec["requires"]
145+
spec["requires"] = spec["runtime_requires"] + spec["build_requires"]
146+
# Check that version is a string
147+
dieOnError(not isinstance(spec["version"], basestring),
148+
"In recipe \"%s\": version must be a string" % p)
149+
spec["tag"] = spec.get("tag", spec["version"])
150+
spec["version"] = spec["version"].replace("/", "_")
151+
spec["recipe"] = recipe.strip("\n")
152+
specs[spec["package"]] = spec
153+
packages += spec["requires"]
154+
return (systemPackages, ownPackages, failedRequirements)

0 commit comments

Comments
 (0)