@@ -57,6 +57,37 @@
# BR2_PACKAGE_CLASSPATH=y, improve the script to detect whether the
# necessary host machine requirements are there to build classpath.
+"""autobuild-run - run Buildroot autobuilder
+
+Usage: autobuild-run [options]
+
+Options:
+ -h, --help show this help message and exit
+ -V, --version show version
+ -n, --ninstances NINSTANCES number of parallel instances [default: 1]
+ -j, --njobs NJOBS number of parallel jobs [default: 1]
+ -s, --submitter SUBMITTER name/machine of submitter [default: N/A]
+ --http-login LOGIN username to send results with
+ --http-password PASSWORD password to send results with (for security
+ reasons it is recommended to define this in the
+ config file instead, with user-read permissions
+ only)
+ -c, --config CONFIG path to configuration file
+
+Format of the configuration file:
+
+ All arguments can also be specified in the configuration file specified with
+ --config, using 'key = value' format (not including the leading --
+ characters). For example:
+
+ [main]
+ ninstances = <value>
+ njobs = <value>
+ http-login = <value>
+ http-password = <value>
+ submitter = <value>
+"""
+
import urllib2
import csv
from random import randint
@@ -70,6 +101,7 @@ import sys
import hashlib
import argparse
import ConfigParser
+import docopt
MAX_DURATION = 60 * 60 * 4
VERSION = 1
@@ -522,81 +554,59 @@ def run_instance(instance, njobs, http_login, http_password, submitter, sysinfo)
ret = do_build(instance, njobs, instance_log)
send_results(instance, http_login, http_password, submitter, instance_log, ret)
-def config_get():
- """Get configuration parameters, either from the command line or the config file"""
+# args / config file merging inspired by:
+# https://github.com/docopt/docopt/blob/master/examples/config_file_example.py
- epilog_text = """
-Format of the configuration file:
+def load_ini_config(configfile):
+ """Load configuration from file, returning a docopt-like dictionary"""
- [main]
- ninstances = <value>
- njobs = <value>
- http-login = <value>
- http-password = <value>
- submitter = <value>
-"""
+ if not os.path.exists(configfile):
+ print "ERROR: configuration file %s does not exist" % configfile
+ sys.exit(1)
+
+ config = ConfigParser.RawConfigParser()
+ if not config.read(configfile):
+ print "ERROR: cannot parse configuration file %s" % configfile
+ sys.exit(1)
+
+ # Prepend '--' to options specified in the config file, so they can be
+ # merged with those given on the command-line
+ return dict(('--%s' % key, value) for key, value in config.items('main'))
- parser = argparse.ArgumentParser(description='Run Buildroot autobuilder',
- formatter_class=argparse.RawDescriptionHelpFormatter,
- epilog=epilog_text)
- parser.add_argument("--ninstances", '-n', metavar="NINSTANCES",
- help="Number of parallel instances", default=None)
- parser.add_argument("--njobs", '-j', metavar="NJOBS",
- help="Number of parallel jobs", default=None)
- parser.add_argument("--submitter", '-s', metavar="SUBMITTER",
- help="Name/machine of submitter")
- parser.add_argument("--config", '-c', metavar="CONFIG",
- help="Path to configuration file")
- args = parser.parse_args()
-
- ninstances = 1
- njobs = 1
- http_login = None
- http_password = None
- submitter = "N/A"
-
- if args.config:
- if not os.path.exists(args.config):
- print "ERROR: configuration file %s does not exist" % args.config
- sys.exit(1)
- parser = ConfigParser.RawConfigParser()
- if not parser.read([args.config]):
- print "ERROR: cannot parse configuration file %s" % args.config
- sys.exit(1)
- if parser.has_option('main', 'ninstances'):
- ninstances = parser.getint('main', 'ninstances')
- if parser.has_option('main', 'njobs'):
- njobs = parser.getint('main', 'njobs')
- if parser.has_option('main', 'http-login'):
- http_login = parser.get('main', 'http-login')
- if parser.has_option('main', 'http-password'):
- http_password = parser.get('main', 'http-password')
- if parser.has_option('main', 'submitter'):
- submitter = parser.get('main', 'submitter')
-
- if args.njobs:
- njobs = int(args.njobs)
- if args.ninstances:
- ninstances = int(args.ninstances)
- if args.submitter:
- submitter = args.submitter
-
- return (ninstances, njobs, http_login, http_password, submitter)
+
+def merge(dict_1, dict_2):
+ """Merge two dictionaries.
+
+ Values that evaluate to true take priority over falsy values.
+ `dict_1` takes priority over `dict_2`.
+
+ """
+ return dict((str(key), dict_1.get(key) or dict_2.get(key))
+ for key in set(dict_2) | set(dict_1))
def main():
check_version()
sysinfo = SystemInfo()
- (ninstances, njobs, http_login, http_password, submitter) = config_get()
- check_requirements(http_login, http_password)
- if http_login is None or http_password is None:
+
+ args = docopt.docopt(__doc__, version=VERSION)
+
+ if args['--config']:
+ ini_config = load_ini_config(args['--config'])
+ # merge config/args, priority given to config
+ args = merge(ini_config, args)
+
+ check_requirements(args['--http-login'], args['--http-password'])
+ if args['--http-login'] is None or args['--http-password'] is None:
print "WARN: due to the lack of http login/password details, results will not be submitted"
print "WARN: tarballs of results will be kept locally only"
def sigterm_handler(signum, frame):
os.killpg(os.getpgid(os.getpid()), signal.SIGTERM)
sys.exit(1)
processes = []
- for i in range(0, ninstances):
- p = Process(target=run_instance, args=(i, njobs, http_login, http_password, submitter, sysinfo))
+ for i in range(0, int(args['--ninstances'])):
+ p = Process(target=run_instance, args=(i, int(args['--njobs']),
+ args['--http-login'], args['--http-password'],
+ args['--submitter'], sysinfo))
p.start()
processes.append(p)
signal.signal(signal.SIGTERM, sigterm_handler)