Skip to content
This repository was archived by the owner on Jan 27, 2019. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions conf/oe-lite.conf
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ LD_LIBRARY_PATH_VAR = "LD_LIBRARY_PATH"
LD_LIBRARY_PATH_VAR:BUILD_KERNEL_darwin = "DYLD_LIBRARY_PATH"

BLACKLIST_VAR = "BLACKLIST_VAR BLACKLIST_PREFIX"
BLACKLIST_VAR += "ASSUME_PROVIDED_MIN ASSUME_PROVIDED_MAX"

# Variables which are only used by the initialization code and which
# should not get inherited down to layer/recipe/task metadata.
Expand Down
4 changes: 4 additions & 0 deletions conf/provided/minimal.conf
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,7 @@ native:shasum \
native:gawk native:sed native:grep \
native:wget \
"

ASSUME_PROVIDED_MIN = "\
native:git_1.8.4 \
"
2 changes: 1 addition & 1 deletion lib/oelite/cmd/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
manifest_cmds = [ "bake", "setup", "show", "cherry", "autodoc", "add-layer" ]
manifest_cmds = [ "bake", "setup", "show", "cherry", "autodoc", "add-layer", "show-provided" ]
185 changes: 185 additions & 0 deletions lib/oelite/cmd/show_provided.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
import oebakery
import oelite.baker
import logging
from oelite.parse import confparse
import re
import subprocess
import bb.utils

description = "Detect and print versions of provided utils"

def add_parser_options(parser):
oelite.baker.add_show_parser_options(parser)
parser.add_option("-v", "--verbose",
action="store_true",
help="Be more chatty")
parser.add_option("-c", "--check",
action="store_true",
help="Check detected version against required min and max")
return

def parse_args(options, args):
if options.verbose:
logging.getLogger().setLevel(logging.DEBUG)
else:
logging.getLogger().setLevel(logging.INFO)

INITIAL_OE_IMPORTS = "sys os time"

# One must describe how to get the version of each util. Fortunately,
# many utilities support a simple "--version" flag, and for most, the
# command to call is the same as what's in the ASSUME_PROVIDED (an
# exception is native:pkgconfig, where the command is pkg-config). So
# we don't need to be very verbose.
#
# extract: a regexp string which is matched against the output from
# the command. Should contain a single capture group, from which we'll
# get the version string
#
# ret: Some utilities exit with a non-zero value when asked to print
# their version (sometimes because they don't actually explicitly
# support --version or any other flag, but they display their version
# nevertheless as a side effect of telling the user he did something
# wrong...). This can be set to an integer or list of integers of
# expected return values.
data = dict()
data["native:binutils"] = dict(cmd = "ld")
data["native:coreutils"] = dict(cmd = "ls")
data["native:mercurial"] = dict(cmd = "hg")
data["native:mtd-utils-mkfs-jffs2"] = dict(cmd = "mkfs.jffs2", ret = 255)
data["native:perl"] = dict(extract = r"\(v([0-9][0-9.a-zA-Z_-]+)\)")
data["native:pkgconfig"] = dict(cmd = "pkg-config")
data["native:python-runtime"] = dict(cmd = "python")
data["native:texinfo"] = dict(cmd = "texindex")
data["native:unzip"] = dict(args = "-v")
data["native:util-linux"] = dict(cmd = "getopt")

def fill_defaults(data, assume_provided):
# Provide an empty dict for those where we don't have any
# requirements. That makes it easy for us to (try to) run the
# default "get the version provided", possibly fixing up those
# where that fails.
for name in assume_provided:
if name in data:
continue
# Libraries would need to be handled in some other way...
if name.startswith("native:lib"):
continue
data[name] = dict()
for name in data:
param = data[name]
if not "cmd" in param:
if ":" in name:
param["cmd"] = name.split(":", 1)[1]
else:
param["cmd"] = name

if not "args" in param:
param["args"] = "--version"
if isinstance(param["args"], basestring):
param["args"] = [param["args"]]

if not "extract" in param:
param["extract"] = r"\b([0-9][0-9.a-zA-Z_-]+)\b"

if not "ret" in param:
param["ret"] = 0
if (isinstance(param["ret"], int)):
param["ret"] = [param["ret"]]

def fill_versions(data, key, versions):
for util in versions:
u, v = util.split("_", 1)
if u in data:
data[u][key] = v

def get_provided_version(p):
param = data.get(p)
if not param:
return None

args = [param["cmd"]] + param["args"]
cmdstring = " ".join(args)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are you flattening args? subprocess.Popen() is capable of taken args as a sequence, and in case you have args with whitespace, you effectively split the arguments this way.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please look at the Popen call - I do pass args as a list. cmdstring is only used in warning messages. Of course that could end up being slightly confusing if there are arguments with whitespace, but I really don't want to complicate this with trying to get shell quoting right.

try:
process = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
except OSError as e:
logging.warn("%s: failed to execute '%s': %s", p, cmdstring, e)
return None
output = process.communicate()[0]

if process.returncode not in param["ret"]:
logging.warn("%s: command '%s' exited with status %d, expected one of %s" %
(p, cmdstring, process.returncode, str(param["ret"])))
if output:
logging.debug("\n".join(map(lambda x: " " + x, output.split("\n"))))
return None

match = re.search(param["extract"], output)
if not match:
logging.info("%s: regexp '%s' did not match output")
if output:
logging.warn("\n".join(map(lambda x: " " + x, output.split("\n"))))
return None
return match.group(1)

def show_provided(p):
version = get_provided_version(p)
if version:
logging.info("%s: found version %s", p, version)
return None

def check_provided(p):
param = data.get(p)
if not param or (not param.get("min") and not param.get("max")):
logging.debug("%s: no version requirements defined" % p)
return None
logging.debug("checking %s" % p)

version = get_provided_version(p)

logging.debug("%s: found version %s", p, version)
minversion = param.get("min")
maxversion = param.get("max")
ok = True
if minversion:
if bb.utils.vercmp_string(version, param["min"]) < 0:
logging.warn("%s: has version %s, expected minumum %s", p, version, minversion)
ok = False
if maxversion:
if bb.utils.vercmp_string(version, param["max"]) > 0:
logging.warn("%s: has version %s, expected maximum %s", p, version, maxversion)
ok = False

return ok

def run(options, args, config):
ret = 0
config = oelite.meta.DictMeta(config)
config["OE_IMPORTS"] = INITIAL_OE_IMPORTS
config.pythonfunc_init()

confparser = confparse.ConfParser(config)
confparser.parse("conf/oe-lite.conf")

assume_provided = sorted(config.get("ASSUME_PROVIDED").split())
fill_defaults(data, assume_provided)
assume_min = (config.get("ASSUME_PROVIDED_MIN") or "").split()
fill_versions(data, "min", assume_min)
assume_max = (config.get("ASSUME_PROVIDED_MAX") or "").split()
fill_versions(data, "max", assume_max)

func = show_provided
if options.check:
func = check_provided

for p in assume_provided:
ok = func(p)
if ok is None:
continue
if ok:
logging.info("%s: OK" % p)
else:
logging.info("%s: not OK" % p)
ret = 1

return ret