From c6a734320a448040f2ae51088170de2b9f70b3d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lorenz=20H=C3=BCdepohl?= <lorenz.huedepohl@rzg.mpg.de> Date: Fri, 10 Nov 2017 14:13:20 +0100 Subject: [PATCH] New commands `mpcdf_info`, `mpcdf_enable_repositories` --- .gitignore | 1 + mpcdf-repositories.py | 138 ----------------------------- mpcdf_common.py | 112 ++++++++++++++++++++++++ mpcdf_enable_repositories.py | 109 +++++++++++++++++++++++ mpcdf_info.py | 78 +++++++++++++++++ mpcdf_setup_repos.py | 164 +++++++++++++++++++++++++++++++++++ 6 files changed, 464 insertions(+), 138 deletions(-) create mode 100644 .gitignore delete mode 100755 mpcdf-repositories.py create mode 100644 mpcdf_common.py create mode 100644 mpcdf_enable_repositories.py create mode 100644 mpcdf_info.py create mode 100644 mpcdf_setup_repos.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0d20b64 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.pyc diff --git a/mpcdf-repositories.py b/mpcdf-repositories.py deleted file mode 100755 index 0558452..0000000 --- a/mpcdf-repositories.py +++ /dev/null @@ -1,138 +0,0 @@ -#!/usr/bin/python2 -from __future__ import print_function -import sys -import argparse -from xml.etree import ElementTree - -import osc -import osc.conf -import osc.core -import osc.cmdln - -all_compilers = "gcc_4_9 gcc_5 gcc_6 gcc_7 intel_16_0 intel_17_0" -all_mpis = "openmpi_1_10 impi_5_1_3 impi_2017_3" -all_cudas = "cuda_8" -default_distribution = "SLE_12_SP1" -default_architectures = "x86_64" - -def valid_mpi(compiler, mpi): - if compiler == "intel_17_0": - if mpi.startswith("impi"): - return mpi == "impi_2017_3" - if compiler == "intel_16_0": - if mpi.startswith("impi"): - return mpi == "impi_5_1_3" - return True - -@osc.cmdln.option('--distribution', - default=default_distribution, - help="Base distribution [default: %default]") - -@osc.cmdln.option('--architectures', - default=default_architectures, - help="Architectures [default: %default]") - -@osc.cmdln.option('--compilers', - default=all_compilers, - help="List of compilers, [default: %default]") - -@osc.cmdln.option('--mpis', - default=all_mpis, - help="List of MPI libraries, [default: %default]") - -@osc.cmdln.option('--cudas', - default=all_cudas, - help="List of CUDA versions, [default: %default]") - -@osc.cmdln.option('--parent', metavar="PARENT", - help="Setup the repositories to be based on the upstream project PARENT (e.g. for home: projects)") - -@osc.cmdln.option('-n', '--dry-run', action="store_true", - help="Do not actually run anything but output the resulting XML configuration") -def do_mpcdf_repositories(self, subcmd, opts, *args): - """${cmd_name}: Create all repository combinations for an MPCDF project - - Given a list of compilers, MPI libraries, and possibly CUDA versions, this command - creates repositories for all the resulting combinations - - Usage: - osc ${cmd_name} [PROJECT] - - ${cmd_option_list} - - """ - - if len(args) == 0: - if is_project_dir(os.curdir) or is_package_dir(os.curdir): - project = osc.core.store_read_project(os.curdir) - else: - raise oscerr.WrongArgs('Specify PROJECT or run command in an osc checkout directory') - - elif len(args) == 1: - project, = args - else: - raise oscerr.WrongArgs("Too many arguments") - - compilers = opts.compilers.split() - architectures = opts.architectures.split() - mpis = opts.mpis.split() - cudas = opts.cudas.split() - - project_meta = osc.core.show_project_meta(osc.conf.config["apiurl"], project) - root = ElementTree.fromstringlist(project_meta) - - # Remove existing repositories - for repo in root.findall("./repository"): - root.remove(repo) - - def repo(name, *dependencies): - r = ElementTree.SubElement(root, "repository") - r.set("name", name) - r.text="\n " - def path(project, repo): - p = ElementTree.SubElement(r, "path") - p.set("project", project) - p.set("repository", repo) - p.tail="\n " - - if opts.parent: - path(opts.parent, name) - for dep_project, dep_repo in dependencies: - path(dep_project, dep_repo) - - for arch in architectures: - a = ElementTree.SubElement(r, "arch") - a.text = arch - a.tail = "\n " - a.tail = "\n " - r.tail = "\n " - - repo("System", ("distributions", opts.distribution)) - - for compiler in sorted(compilers): - repo(compiler, (project, "System")) - - for mpi in sorted(mpis): - for compiler in sorted(compilers): - if not valid_mpi(compiler, mpi): - continue - repo(mpi + "_" + compiler, (project, compiler)) - - for cuda in sorted(cudas): - repo(cuda, (project, "System")) - for compiler in sorted(compilers): - repo(cuda + "_" + compiler, (project, cuda), (project, compiler)) - for mpi in sorted(mpis): - for compiler in sorted(compilers): - if not valid_mpi(compiler, mpi): - continue - repo(cuda + "_" + mpi + "_" + compiler, - (project, cuda + "_" + compiler), - (project, mpi + "_" + compiler)) - - root.getchildren()[-1].tail = "\n" - meta = ET.tostring(root, encoding=osc.core.ET_ENCODING) - if opts.dry_run: - print(meta) - else: - osc.core.edit_meta("prj", project, data=meta) diff --git a/mpcdf_common.py b/mpcdf_common.py new file mode 100644 index 0000000..f07044f --- /dev/null +++ b/mpcdf_common.py @@ -0,0 +1,112 @@ +from __future__ import print_function + +__all__ = ["valid_mpi", "valid_cuda", "get_attribute", "mpcdf_enable_repositories"] + +import osc +import osc.conf +import osc.core + +from xml.etree import ElementTree + + +def valid_mpi(compiler, mpi): + """ + It might be possible to use Intel MPI libararies and compilers from + different Parallel Studio packages, but I currently do not want to support + it + """ + if compiler == "intel_17_0": + if mpi.startswith("impi"): + return mpi == "impi_2017_3" + if compiler == "intel_16_0": + if mpi.startswith("impi"): + return mpi == "impi_5_1_3" + return True + + +def valid_cuda(cuda, compiler): + """ + The CUDA distribution only works with certain gcc versions, + this little function takes care that only supported combinations + are allowed + """ + if cuda == "cuda_8": + return compiler == "gcc_5" + return False + + +def get_attribute(api_url, project, package, attribute, with_project=False): + + attribute_meta = osc.core.show_attribute_meta(api_url, project, package, None, attribute, False, with_project) + if attribute_meta is None: + raise oscerr.APIError("Cannot fetch value for attribute '{0}' from {1}".format(attribute, (project, package))) + + root = ElementTree.fromstringlist(attribute_meta) + attribute = root.find("./attribute") + if attribute is not None: + return list(value.text for value in attribute.findall("./value")) + else: + raise Exception("Attribute not set") + + +def mpcdf_enable_repositories(api_url, project, package, verbose=False): + + root = ElementTree.fromstringlist(osc.core.show_package_meta(api_url, project, package)) + build = root.find("./build") + for enable in build.findall("./enable"): + build.remove(enable) + + compilers = get_attribute(api_url, project, package, "MPCDF:compiler_modules", with_project=True) + mpis = get_attribute(api_url, project, package, "MPCDF:mpi_modules", with_project=True) + cudas = get_attribute(api_url, project, package, "MPCDF:cuda_modules", with_project=True) + + def enable(name): + node = ElementTree.Element("enable") + node.set("repository", name) + node.tail = "\n " + build.insert(0, node) + if verbose: + print("Enabling", name) + + try: + enable_repos = get_attribute(api_url, project, package, "MPCDF:enable_repositories") + except: + if verbose: + print("Warning: Could not get attribute MPCDF:enable_repositories for package {0}".format(package)) + return False + + for flag in enable_repos: + + if flag == "system": + enable("System") + + if flag == "compilers": + for compiler in compilers: + enable(compiler) + + if flag == "mpi": + for compiler in compilers: + for mpi in mpis: + if valid_mpi(compiler, mpi): + enable(mpi + "_" + compiler) + + if flag == "cuda": + for cuda in cudas: + for compiler in compilers: + if valid_cuda(cuda, compiler): + enable(cuda + "_" + compiler) + + if flag == "cuda_mpi": + for cuda in cudas: + for compiler in compilers: + if valid_cuda(cuda, compiler): + for mpi in mpis: + if valid_mpi(compiler, mpi): + enable(cuda + "_" + compiler) + + build.getchildren()[-1].tail = "\n " + + pkg_meta = ET.tostring(root, encoding=osc.core.ET_ENCODING) + + osc.core.edit_meta("pkg", (project, package), data=pkg_meta) + return True diff --git a/mpcdf_enable_repositories.py b/mpcdf_enable_repositories.py new file mode 100644 index 0000000..e9cc2bf --- /dev/null +++ b/mpcdf_enable_repositories.py @@ -0,0 +1,109 @@ +#!/usr/bin/python2 +from __future__ import print_function +import sys +import argparse +from functools import partial +from xml.etree import ElementTree + +from mpcdf_common import * + +import osc +import osc.conf +import osc.core +import osc.cmdln + +@osc.cmdln.option('--cuda-mpi', action="store_true", + help="Enable for all CUDA+MPI repositories") +@osc.cmdln.option('--cuda', action="store_true", + help="Enable for all CUDA repositories") +@osc.cmdln.option('--mpi', action="store_true", + help="Enable for all MPI module repositories") +@osc.cmdln.option('--compilers', action="store_true", + help="Enable for all compiler module repositories") +@osc.cmdln.option('--system', action="store_true", + help="Enable for System repository") +@osc.cmdln.option('--reset', action="store_true", + help="Re-create the set of enabled repositories from the MPCDF:enable_repositores attribute") +@osc.cmdln.option('--set', action="store_true", + help="Modify the set of enabled repositories, without this the current setup is displayed") +def do_mpcdf_enable_repositories(self, subcmd, opts, *args): + """${cmd_name}: Select all appropriate repositories for an MPCDF package + + Due to the large number of repository combinations at MPCDF it is + cumbersome to enable all the appropriate repositories for a given package + by hand. + + This command allows you to set the <enable/> flags for certain kinds of + repositories at once. + + Without --set this command only displays the current configuration. + + It is possible to combine this with user-defined <disabled/> flags that + override the settings of this command, for example to disable the build of + the package with a certain repository from a given set (e.g. one + troublesome compiler) + + Usage: + osc ${cmd_name} [[PROJECT] PACKAGE] + osc ${cmd_name} --set [ flags ] [[PROJECT] PACKAGE] + osc ${cmd_name} --reset [[PROJECT] PACKAGE] + + ${cmd_option_list} + """ + + if len(args) == 0: + if is_package_dir(os.curdir): + package = osc.core.store_read_package(os.curdir) + project = osc.core.store_read_project(os.curdir) + else: + raise oscerr.WrongArgs('Specify PACKAGE or run command in an osc package checkout directory') + + elif len(args) == 1: + package, = args + project = osc.core.store_read_project(os.curdir) + + elif len(args) == 2: + project, package = args + else: + raise oscerr.WrongArgs("Too many arguments") + + api_url = self.get_api_url() + + if opts.set: + root = ElementTree.fromstringlist(osc.core.show_attribute_meta(api_url, project, package, None, "MPCDF:enable_repositories", False, False)) + + attribute = root.find("./attribute") + if attribute is None: + attribute = ElementTree.SubElement(root, "attribute") + attribute.set("namespace", "MPCDF") + attribute.set("name", "enable_repositories") + + for value in attribute.findall("./value"): + attribute.remove(value) + + def value(content): + v = ElementTree.SubElement(attribute, "value") + v.text = content + + if opts.system: + value("system") + if opts.compilers: + value("compilers") + if opts.mpi: + value("mpi") + if opts.cuda: + value("cuda") + if opts.cuda_mpi: + value("cuda_mpi") + + attr_meta = ET.tostring(root, encoding=osc.core.ET_ENCODING) + + url = osc.core.makeurl(api_url, ["source", project, package, "_attribute"]) + resp = ElementTree.fromstringlist(osc.core.streamfile(url, http_POST, data=attr_meta)) + if resp.find("./summary").text != "Ok": + raise osc.core.oscerr.APIError("Could not store attribute MPCDF:enabled_repositories") + + if opts.reset or opts.set: + mpcdf_enable_repositories(api_url, project, package, verbose=True) + else: + print("Enabled for:", *get_attribute(api_url, project, package, "MPCDF:enable_repositories")) diff --git a/mpcdf_info.py b/mpcdf_info.py new file mode 100644 index 0000000..36b12b1 --- /dev/null +++ b/mpcdf_info.py @@ -0,0 +1,78 @@ +#!/usr/bin/python2 +from __future__ import print_function +import sys +import argparse + +from mpcdf_common import * + +import osc +import osc.conf +import osc.core +import osc.cmdln + +def do_mpcdf_info(self, subcmd, opts, *args): + """${cmd_name}: Basic information about an MPCDF OBS project + + Usage: + osc ${cmd_name} [PROJECT] + + ${cmd_option_list} + + """ + + if len(args) == 0: + if is_project_dir(os.curdir) or is_package_dir(os.curdir): + project = osc.core.store_read_project(os.curdir) + else: + raise oscerr.WrongArgs('Specify PROJECT or run command in an osc checkout directory') + + elif len(args) == 1: + project, = args + else: + raise oscerr.WrongArgs("Too many arguments") + + print("Project `{0}`\n".format(project)) + + api_url = self.get_api_url() + + def print_attribute(description, attribute): + values = list(sorted(get_attribute(api_url, project, None, attribute))) + if values: + print(" {0}:\n ".format(description) + "\n ".join(values)) + print() + + print_attribute("Compilers", "MPCDF:compiler_modules") + print_attribute("MPI libraries", "MPCDF:mpi_modules") + print_attribute("CUDA versions", "MPCDF:cuda_modules") + + table = [] + unmanaged = [] + + for package in osc.core.meta_get_packagelist(api_url, project): + try: + table.append((package, set(get_attribute(api_url, project, package, "MPCDF:enable_repositories")))) + except: + unmanaged.append(package) + + pkg_name_width = reduce(max, (len(t[0]) for t in table)) + pkg_name_width = max(pkg_name_width, len("Package")) + + r = osc.core.http_request("GET", api_url + "/attribute/MPCDF/enable_repositories/_meta") + root = ET.parse(r).getroot() + kinds = list(value.text for value in root.findall("./allowed/value")) + + pkg_name_fmt = "{{:{0}}}".format(pkg_name_width) + + print(" " + pkg_name_fmt.format("Package"), "Enabled repositories") + print(" " + "-" * (pkg_name_width), "-" * len("Enabled repositories")) + + for package, enabled_repo_kind in table: + print(" " + pkg_name_fmt.format(package), ", ".join(filter(lambda q : q in enabled_repo_kind, kinds))) + print() + + if unmanaged: + print(" Unmanaged packages") + print(" " + "-" * (1 + pkg_name_width)) + for package in unmanaged: + print(" " + package) + print() diff --git a/mpcdf_setup_repos.py b/mpcdf_setup_repos.py new file mode 100644 index 0000000..4cddb60 --- /dev/null +++ b/mpcdf_setup_repos.py @@ -0,0 +1,164 @@ +#!/usr/bin/python2 +from __future__ import print_function +import sys +import argparse +from functools import partial +from xml.etree import ElementTree + +from mpcdf_common import * + +import osc +import osc.conf +import osc.core +import osc.cmdln + +default_distribution = "SLE_12_SP1" + +@osc.cmdln.option('-n', '--dry-run', action="store_true", + help="Do not actually run anything but output the resulting XML configuration") + +@osc.cmdln.option('--parent', metavar="PARENT", + help="Setup the repositories to be based on the upstream project PARENT (e.g. for home: projects)") + +@osc.cmdln.option('--distribution', + default=default_distribution, + help="Base distribution [default: %default]") +def do_mpcdf_setup_repos(self, subcmd, opts, *args): + """${cmd_name}: Create all repository combinations for an MPCDF project + + Given a list of compilers, MPI libraries, and possibly CUDA versions, this command + creates repositories for all the resulting combinations + + Usage: + osc ${cmd_name} [PROJECT] + + ${cmd_option_list} + + """ + + if len(args) == 0: + if is_project_dir(os.curdir) or is_package_dir(os.curdir): + project = osc.core.store_read_project(os.curdir) + else: + raise oscerr.WrongArgs('Specify PROJECT or run command in an osc checkout directory') + + elif len(args) == 1: + project, = args + else: + raise oscerr.WrongArgs("Too many arguments") + + api_url = self.get_api_url() + + compilers = list(get_attribute(api_url, project, None, "MPCDF:compiler_modules")) + mpis = list(get_attribute(api_url, project, None, "MPCDF:mpi_modules")) + cudas = list(get_attribute(api_url, project, None, "MPCDF:cuda_modules")) + + def project_meta(project): + return ElementTree.fromstringlist(osc.core.show_project_meta(api_url, project)) + + distributions = project_meta("distributions") + dist_repo = distributions.find('./repository[@name="{0}"]'.format(opts.distribution)) + if dist_repo is None: + raise oscerr.WrongArgs("No repository '{0}' is defined in project 'distributions' on the server".format(opts.distribution)) + architectures = list(arch.text for arch in dist_repo.findall("./arch")) + + root = project_meta(project) + + prjconf = osc.core.show_project_conf(api_url, project) + start_marker = "# Autogenerated by osc mpcdf_setup_repos, do not edit till end of section\n" + end_marker = "# End of autogenerated section\n" + + try: + start = prjconf.index(start_marker) + end = prjconf.index(end_marker) + except ValueError: + start = None + end = len(prjconf) + + prjconf_head = "".join(prjconf[:start]) + prjconf_tail = "".join(prjconf[end+1:]) + prjconf = [start_marker] + + # Remove existing repositories + for repo in root.findall("./repository"): + root.remove(repo) + + def repo(name, *dependencies, **kwargs): + is_mpi = kwargs.pop("mpi", False) + is_cuda = kwargs.pop("cuda", False) + if kwargs: + raise Exception("Invalid argument") + r = ElementTree.SubElement(root, "repository") + r.set("name", name) + r.text="\n " + def path(project, repo): + p = ElementTree.SubElement(r, "path") + p.set("project", project) + p.set("repository", repo) + p.tail="\n " + + if opts.parent: + path(opts.parent, name) + for dep_project, dep_repo in dependencies: + path(dep_project, dep_repo) + + for arch in architectures: + a = ElementTree.SubElement(r, "arch") + a.text = arch + a.tail = "\n " + a.tail = "\n " + r.tail = "\n " + + prjconf.append("%if %_repository == {0}".format(name)) + prjconf.append("Macros:") + prjconf.append("%is_mpi_repository {0}".format(1 if is_mpi else 0)) + prjconf.append("%is_cuda_repository {0}".format(1 if is_cuda else 0)) + prjconf.append(":Macros") + prjconf.append("%endif") + prjconf.append("") + + + repo("System", ("distributions", opts.distribution)) + + for compiler in compilers: + repo(compiler, (project, "System")) + + for mpi in filter(partial(valid_mpi, compiler), mpis): + repo(mpi + "_" + compiler, (project, compiler), mpi=True) + + for cuda in cudas: + for compiler in filter(partial(valid_cuda, cuda), compilers): + repo(cuda + "_" + compiler, (project, compiler), cuda=True) + for mpi in filter(partial(valid_mpi, compiler), mpis): + repo(cuda + "_" + mpi + "_" + compiler, + (project, cuda + "_" + compiler), + (project, mpi + "_" + compiler), + mpi=True, cuda=True) + + root.getchildren()[-1].tail = "\n" + prj = ET.tostring(root, encoding=osc.core.ET_ENCODING) + + prjconf.append(end_marker) + prjconf = prjconf_head + "\n".join(prjconf) + prjconf_tail + + if opts.dry_run: + if len(args) == 0: + arg = "" + else: + arg = " " + project + print("osc meta prj{0} -F - <<EOF\n{1}\nEOF\n".format(arg, prj)) + print("osc meta prjconf{0} -F - <<EOF\n{1}\nEOF\n".format(arg, prjconf)) + else: + # First set-up the <enable/> flags, that way no + # spurious builds are launched + for package in osc.core.meta_get_packagelist(api_url, project): + print("Updating repositories for", package) + if not mpcdf_enable_repositories(api_url, project, package): + print("ATTENTION: Not changing unmanaged package {0}".format(package)) + + # Update repositories + print("Updating prj meta") + osc.core.edit_meta("prj", project, data=prj) + print("Updating prjconf meta") + osc.core.edit_meta("prjconf", project, data=prjconf) + -- GitLab