From 61efc5d53e61929489a1fefe3639e0ec203aedef Mon Sep 17 00:00:00 2001 From: Samuel Gaist <samuel.gaist@idiap.ch> Date: Thu, 10 Jan 2019 13:45:36 +0100 Subject: [PATCH] [doc] Add script to generate list of dependencies license Requires pip-licenses --- doc/get_dep_licenses.py | 203 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 203 insertions(+) create mode 100755 doc/get_dep_licenses.py diff --git a/doc/get_dep_licenses.py b/doc/get_dep_licenses.py new file mode 100755 index 00000000..46a73624 --- /dev/null +++ b/doc/get_dep_licenses.py @@ -0,0 +1,203 @@ +#!/usr/bin/env python +# vim: set fileencoding=utf-8 : + +############################################################################### +# # +# Copyright (c) 2019 Idiap Research Institute, http://www.idiap.ch/ # +# Contact: beat.support@idiap.ch # +# # +# This file is part of the beat.editor module of the BEAT platform. # +# # +# Commercial License Usage # +# Licensees holding valid commercial BEAT licenses may use this file in # +# accordance with the terms contained in a written agreement between you # +# and Idiap. For further information contact tto@idiap.ch # +# # +# Alternatively, this file may be used under the terms of the GNU Affero # +# Public License version 3 as published by the Free Software and appearing # +# in the file LICENSE.AGPL included in the packaging of this file. # +# The BEAT platform is distributed in the hope that it will be useful, but # +# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY # +# or FITNESS FOR A PARTICULAR PURPOSE. # +# # +# You should have received a copy of the GNU Affero Public License along # +# with the BEAT platform. If not, see http://www.gnu.org/licenses/. # +# # +############################################################################### + +"""Dumps the license of all dependencies + +Usage: + %(prog)s [-v ... | --verbose ...] + %(prog)s (--help | -h) + +Options: + -h, --help Show this screen + -v, --verbose Increases the output verbosity level +""" + +import json +import logging +import os +import re +import sys + +from subprocess import PIPE # nosec +from subprocess import Popen # nosec + +from docopt import docopt + + +def _run_cmd(cmd_list): + """Run given command""" + + try: + p = Popen(cmd_list, stdout=PIPE, stderr=PIPE) # nosec + except OSError: + raise Exception("could not invoke %r\n" % cmd_list) + return json.loads(p.communicate()[0]) + + +def _call_conda(extra_args): + """ call conda with the list of extra arguments, and return the tuple + stdout, stderr + """ + + cmd_list = [os.environ.get("CONDA_EXE")] + + cmd_list.extend(extra_args) + + return _run_cmd(cmd_list) + + +def _get_pip_info(): + """ Get license info from pip""" + + return _run_cmd(["pip-licenses", "--format-json"]) + + +def _clean_pkg_name(name): + """ Cleanup package name: + - lower case + - replace minus and underscore by only minus + """ + + return re.sub(r"[_-]", "-", name.lower()) + + +if __name__ == "__main__": + + arguments = sys.argv[1:] + + prog = os.path.basename(sys.argv[0]) + completions = dict(prog=prog) + + args = docopt(__doc__ % completions, argv=arguments, options_first=True) + + # Setup the logging + formatter = logging.Formatter( + fmt="[%(asctime)s - License - %(name)s] %(levelname)s: %(message)s", + datefmt="%d/%b/%Y %H:%M:%S", + ) + + handler = logging.StreamHandler() + handler.setFormatter(formatter) + + logger = logging.getLogger("beat.editor") + logger.addHandler(handler) + + if args["--verbose"] == 1: + logger.setLevel(logging.INFO) + elif args["--verbose"] == 2: + logger.setLevel(logging.DEBUG) + elif args["--verbose"] >= 3: + logger.setLevel(logging.DEBUG) + else: + logger.setLevel(logging.WARNING) + + list_args = ["list", "--json"] + logger.info("listing conda packages") + pkg_list = _call_conda(list_args) + logger.info("done") + + info_args = ["info", "-l", "--json"] + info_args += ["{}={}".format(item["name"], item["version"]) for item in pkg_list] + + logger.info("loading conda packages information") + pkg_info_list = _call_conda(info_args) + logger.info("done") + + cleaned_pkg_info_list = [] + logger.info("extracting exact package version data") + for pkg in pkg_list: + key = "{}={}".format(pkg["name"], pkg["version"]) + build_string = pkg["build_string"] + pkg_info = [ + item for item in pkg_info_list[key] if item["build"] == build_string + ] + if len(pkg_info) > 0: + cleaned_pkg_info_list.append(pkg_info[0]) + else: + logger.warning( + "No information available for {}, {}".format(key, build_string) + ) + logger.info("done") + + logger.info("loading pip packages information") + pip_pkg_info_list = _get_pip_info() + logger.info("done") + + logger.info("unifying data") + pkg_names = [_clean_pkg_name(pkg["name"]) for pkg in pkg_list] + + cleaned_pkg_info_list += [ + {k.casefold(): v for k, v in item.items()} + for item in pip_pkg_info_list + if _clean_pkg_name(item["Name"]) not in pkg_names + ] + logger.info("done") + + cleaned_pkg_info_list = sorted(cleaned_pkg_info_list, key=lambda x: x["name"]) + + name_length = 0 + version_length = 0 + license_length = 0 + + for item in cleaned_pkg_info_list: + name_length = max(name_length, len(item["name"])) + version_length = max(version_length, len(item["version"])) + if "license" not in item: + logger.warning( + "Package has no license information: {}".format(item["name"]) + ) + item["license"] = "Unknown" + license_length = max(license_length, len(item["license"])) + + filler = "|{0:-<{fill_name}}|{0:-<{fill_version}}|{0:-<{fill_license}}|\n".format( + "", + fill_name=name_length + 2, + fill_version=version_length + 2, + fill_license=license_length + 2, + ) + + text = filler + text += "| {0:<{fill_name}} | {1:<{fill_version}} | {2:<{fill_license}} |\n".format( + "Name", + "Version", + "License", + fill_name=name_length, + fill_version=version_length, + fill_license=license_length, + ) + + text += filler + + for item in cleaned_pkg_info_list: + text += "| {name:<{fill_name}} | {version:<{fill_version}} | {license:<{fill_license}} |\n".format( + **item, + fill_name=name_length, + fill_version=version_length, + fill_license=license_length + ) + + sys.stdout.write(text) -- GitLab